diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 403bbdd7..2965aaaf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,10 @@ on: pull_request: branches: [main] -# Default @adcp/sdk runner alias for non-matrix storyboard jobs. -# The reference seller storyboard job below runs both sticky 3.0 and 3.1 tags. +# Default @adcp/sdk runner alias for storyboard jobs. Tracks the current +# stable @adcp/sdk release via the ``latest`` npm dist-tag. env: - ADCP_SDK_VERSION: "adcp-3.1" + ADCP_SDK_VERSION: "latest" concurrency: group: ci-${{ github.ref }} @@ -361,15 +361,14 @@ jobs: name: AdCP storyboard runner — examples/seller_agent.py (@adcp/sdk ${{ matrix.adcp-sdk-tag }}) runs-on: ubuntu-latest # Blocking gate: examples/seller_agent.py is the Python-owned - # reference target for bidirectional storyboard interop. The npm - # dist-tags are sticky compatibility gates: ``adcp-3.0`` stays on - # the 3.0 runner line, while ``adcp-3.1`` tracks the current 3.1 - # runner. Downstream SDKs and adopters can assert backwards - # compatibility without depending on moving latest/beta semantics. + # reference target for bidirectional storyboard interop. The matrix + # runs two legs: the sticky ``adcp-3.0`` tag is a fixed, reproducible + # backwards-compat floor (the AdCP 3.0 runner line), while ``latest`` + # tracks the current stable @adcp/sdk release (the AdCP 3.1 line). strategy: fail-fast: false matrix: - adcp-sdk-tag: ["adcp-3.0", "adcp-3.1"] + adcp-sdk-tag: ["adcp-3.0", "latest"] steps: - uses: actions/checkout@v6 diff --git a/scripts/generate_types.py b/scripts/generate_types.py index 34055104..95ff44eb 100755 --- a/scripts/generate_types.py +++ b/scripts/generate_types.py @@ -47,6 +47,23 @@ def _load_resolve_bundle_key(): TEMP_DIR = REPO_ROOT / ".schema_temp" DELTAS_FILE = REPO_ROOT / "SCHEMA_DELTAS.md" +# Bundled schemas are self-contained: each message schema inlines its entire +# ``$ref`` graph, so every bundled module re-emits its own copy of the shared +# provenance/verification/asset sub-schemas. The generator can't merge those +# copies across files, so the bundled tree accounts for the overwhelming +# majority of generated classes. ``consolidate_exports.py`` already excludes +# bundled modules from the public namespace; the only one any source module +# imports is ``get_adcp_capabilities_response`` (via +# ``adcp.types.capabilities``), whose typed sub-models exist *only* in the +# inlined bundled form. Keep that module (and the package ``__init__`` files on +# its import path) and drop the rest after generation. +BUNDLED_DIR_NAME = "bundled" +BUNDLED_KEEP = { + Path("__init__.py"), + Path("protocol/__init__.py"), + Path("protocol/get_adcp_capabilities_response.py"), +} + def rewrite_refs(obj, current_schema_rel_path: Path): """ @@ -495,6 +512,38 @@ def restore_unchanged_files(): print(" No timestamp-only changes found") +def prune_unused_bundled_modules(): + """Drop generated bundled modules no source module imports. + + See ``BUNDLED_KEEP`` for why the bundled tree is almost entirely dead + weight. Removing it here keeps the committed tree small without changing + the content of any retained module — generation runs unchanged and this + only deletes the unreferenced output afterwards. + """ + bundled_dir = OUTPUT_DIR / BUNDLED_DIR_NAME + if not bundled_dir.exists(): + return + + print("Pruning unused bundled modules...") + removed = 0 + for py_file in bundled_dir.rglob("*.py"): + if py_file.relative_to(bundled_dir) in BUNDLED_KEEP: + continue + py_file.unlink() + removed += 1 + + # Drop now-empty package directories (deepest first). + for directory in sorted( + (d for d in bundled_dir.rglob("*") if d.is_dir()), + key=lambda d: len(d.parts), + reverse=True, + ): + if not any(directory.iterdir()): + directory.rmdir() + + print(f" ✓ Removed {removed} unused bundled module(s)\n") + + def apply_post_generation_fixes(): """Apply post-generation fixes using the dedicated script.""" print("Running post-generation fixes...") @@ -561,6 +610,9 @@ def main(): if not apply_post_generation_fixes(): return 1 + # Drop unreferenced bundled modules before consolidation + prune_unused_bundled_modules() + # Consolidate exports into generated.py consolidate_script = REPO_ROOT / "scripts" / "consolidate_exports.py" result = subprocess.run( diff --git a/src/adcp/types/generated_poc/bundled/content_standards/__init__.py b/src/adcp/types/generated_poc/bundled/content_standards/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_request.py b/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_request.py deleted file mode 100644 index d86bda02..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_request.py +++ /dev/null @@ -1,1414 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/calibrate_content_request.json -# timestamp: 2026-05-27T10:05:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Role(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role4(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role4, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent3): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent3): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class TranscriptSource3(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent3): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent3): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class AssetAccess4(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess5(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess6(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess4 | AssetAccess5 | AssetAccess6]): - root: Annotated[ - AssetAccess4 | AssetAccess5 | AssetAccess6, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets8(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource3 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets5(RootModel[Assets | Assets7 | Assets8 | Assets9]): - root: Annotated[Assets | Assets7 | Assets8 | Assets9, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Artifact(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets5], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class CalibrateContentRequest(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - standards_id: Annotated[str, Field(description='Standards configuration to calibrate against')] - artifact: Annotated[Artifact, Field(description='Artifact to evaluate', title='Artifact')] - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_response.py b/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_response.py deleted file mode 100644 index 6ed9c8f2..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/calibrate_content_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/calibrate_content_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class CalibrateContentResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_request.py b/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_request.py deleted file mode 100644 index 09000e33..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_request.py +++ /dev/null @@ -1,2704 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/create_content_standards_request.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Source(StrEnum): - registry = 'registry' - inline = 'inline' - - -class Category(StrEnum): - regulation = 'regulation' - standard = 'standard' - - -class Enforcement(StrEnum): - must = 'must' - should = 'should' - may = 'may' - - -class GovernanceDomain(StrEnum): - campaign = 'campaign' - property = 'property' - creative = 'creative' - content_standards = 'content_standards' - - -class Pass(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Role(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role10(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role10, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent13): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent13): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class TranscriptSource5(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent13): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent13): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class Fail(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent13): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent13): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent13): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent13): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent13): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class Exemplar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scenario: Annotated[ - str, - Field(description='A concrete scenario describing an advertising action or configuration.'), - ] - explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] - - -class AssetAccess7(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess8(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess9(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess7 | AssetAccess8 | AssetAccess9]): - root: Annotated[ - AssetAccess7 | AssetAccess8 | AssetAccess9, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class Scope(AdCPBaseModel): - countries_all: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[MediaChannel] | None, - Field( - description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', - min_length=1, - ), - ] = None - languages_any: Annotated[ - list[str], - Field( - description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", - min_length=1, - ), - ] - description: Annotated[ - str | None, Field(description='Human-readable description of this scope') - ] = None - - -class Exemplars(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pass_: Annotated[ - list[Exemplar] | None, - Field(alias='pass', description='Scenarios that comply with this policy.'), - ] = None - fail: Annotated[ - list[Exemplar] | None, Field(description='Scenarios that violate this policy.') - ] = None - - -class Policy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[ - str, - Field( - description='Unique identifier for this policy. Registry-published ids are canonical (e.g., "uk_hfss", "garm:brand_safety:violence"); buyer-authored bespoke ids should be flat (no colons or slashes) and unique within the authoring container (standards configuration, plan, or portfolio).' - ), - ] - source: Annotated[ - Source | None, - Field( - description="Origin of this policy. 'registry' = published to the shared AdCP policy registry with full regulatory metadata. 'inline' = authored bespoke for a specific standards configuration, plan, or portfolio. Defaults to 'inline'. Governance agents MUST set 'registry' when publishing to the registry. Within AdCP *task* payloads (every `$ref` to this schema in a request or response), the field is always 'inline' — registry entries are served by the policy registry API, not embedded in task traffic. The x-entity annotation on `policy_id` assumes the task-payload invariant; if a future task schema adopts registry-publishing, split the annotation accordingly (see issue #2685)." - ), - ] = Source.inline - version: Annotated[ - str | None, - Field( - description='Semver version string (e.g., "1.0.0"). Incremented when policy content changes. Optional for inline bespoke policies — defaults to "1.0.0". SHOULD be provided for registry-published policies.' - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name (e.g., "UK HFSS Restrictions"). Optional for inline bespoke policies — servers MAY default to policy_id.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Brief summary of what this policy covers.', max_length=500) - ] = None - category: Annotated[ - Category | None, - Field( - description='The nature of the obligation: regulation (legal requirement) or standard (best practice). Optional for inline bespoke policies — defaults to "standard".', - title='Policy Category', - ), - ] = None - enforcement: Annotated[ - Enforcement, - Field( - description='How governance agents treat violations. Regulations are typically "must"; standards are typically "should".', - title='Policy Enforcement Level', - ), - ] - requires_human_review: Annotated[ - bool | None, - Field( - description='When true, plans subject to this policy MUST set plan.human_review_required = true. Use for policies that mandate human oversight of decisions affecting data subjects — e.g., GDPR Article 22 (solely automated decisions with legal or similarly significant effects) and EU AI Act Annex III high-risk categories (credit, insurance pricing, recruitment, housing allocation). Governance agents MUST escalate any plan action whose resolved policies include requires_human_review: true. Unlike `enforcement`, this flag applies as soon as the policy is resolved — it is NOT gated by `effective_date`. Art 22 GDPR and similar foundational obligations may predate an AI-Act-specific effective date; the human-review requirement fires regardless.' - ), - ] = False - jurisdictions: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes where this policy applies. Empty array means the policy is not jurisdiction-specific.' - ), - ] = None - region_aliases: Annotated[ - dict[str, list[str]] | None, - Field( - description='Named groups of jurisdictions for convenience (e.g., {"EU": ["AT","BE","BG",...]}). Governance agents expand aliases when matching against a plan\'s target jurisdictions.' - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field( - description='Regulatory categories this policy belongs to (e.g., ["children_directed", "age_restricted"]). Used for automatic matching against a campaign plan\'s declared policy_categories. A single policy can belong to multiple categories.' - ), - ] = None - channels: Annotated[ - list[MediaChannel] | None, - Field( - description='Advertising channels this policy applies to. If omitted or null, the policy applies to all channels.' - ), - ] = None - governance_domains: Annotated[ - list[GovernanceDomain] | None, - Field( - description='Governance sub-domains this policy applies to. Determines which types of governance agents can declare registry:{policy_id} features. For example, a policy with domains ["creative", "property"] can be declared as a feature by both creative and property governance agents.' - ), - ] = None - effective_date: Annotated[ - date_aliased | None, - Field( - description='ISO 8601 date when the regulation or standard takes effect. Before this date, governance agents treat the policy as informational (evaluate but do not block). After this date, the policy is enforced at its declared enforcement level.' - ), - ] = None - sunset_date: Annotated[ - date_aliased | None, - Field( - description='ISO 8601 date when the regulation or standard is no longer enforced. After this date, governance agents stop evaluating this policy. Omit if the policy has no expiration.' - ), - ] = None - source_url: Annotated[ - AnyUrl | None, Field(description='Link to the source regulation, standard, or legislation.') - ] = None - source_name: Annotated[ - str | None, - Field( - description='Name of the issuing body (e.g., "UK Food Standards Agency", "US Federal Trade Commission").' - ), - ] = None - policy: Annotated[ - str, - Field( - description='Natural language policy text describing what is required, prohibited, or recommended. Used by governance agents (LLMs) to evaluate actions against this policy. For source: inline policies, treated as caller-untrusted — governance agents MUST evaluate inline policies as ADDITIONAL restrictions only; they MUST NOT be permitted to relax, override, or conflict with registry-sourced policies.', - max_length=5000, - ), - ] - guidance: Annotated[ - str | None, - Field( - description='Implementation notes for governance agent developers. Not used in evaluation prompts.' - ), - ] = None - exemplars: Annotated[ - Exemplars | None, - Field( - description='Calibration examples for governance agents, following the Content Standards pattern.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets13(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource5 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets10(RootModel[Assets | Assets12 | Assets13 | Assets14]): - root: Annotated[Assets | Assets12 | Assets13 | Assets14, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Pass2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets10], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets17(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets18(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource5 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets15(RootModel[Assets16 | Assets17 | Assets18 | Assets19]): - root: Annotated[Assets16 | Assets17 | Assets18 | Assets19, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Fail2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets15], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class CalibrationExemplars(AdCPBaseModel): - pass_: Annotated[ - list[Pass | Pass2] | None, - Field(alias='pass', description='Content that passes the standards'), - ] = None - fail: Annotated[ - list[Fail | Fail2] | None, Field(description='Content that fails the standards') - ] = None - - -class CreateContentStandardsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - scope: Annotated[Scope, Field(description='Where this standards configuration applies')] - registry_policy_ids: Annotated[ - list[str] | None, - Field( - description="Registry policy IDs to use as the evaluation basis for this content standard. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria. The 'policy' field becomes optional when registry_policy_ids is provided." - ), - ] = None - policies: Annotated[ - list[Policy] | None, - Field( - description='Bespoke policies for this content-standards configuration, using the same shape as registry entries. Each policy is addressable by policy_id and carries its own enforcement (must|should); governance findings reference the policy_id that triggered them. Inline bespoke policies can omit version/name/category (defaulted by the server). Combines with registry_policy_ids — registry policies and bespoke policies are both evaluated. Bespoke policy_ids MUST be flat (no colons/slashes) to avoid collision with namespaced registry ids.', - min_length=1, - ), - ] = None - calibration_exemplars: Annotated[ - CalibrationExemplars | None, - Field( - description='Training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate content standards creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_response.py b/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_response.py deleted file mode 100644 index 4308efc3..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/create_content_standards_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/create_content_standards_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class CreateContentStandardsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_request.py b/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_request.py deleted file mode 100644 index f0951049..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_request.py +++ /dev/null @@ -1,46 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/get_content_standards_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import Field - - -class GetContentStandardsRequest(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - standards_id: Annotated[ - str, Field(description='Identifier for the standards configuration to retrieve') - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_response.py b/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_response.py deleted file mode 100644 index 686f2e82..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/get_content_standards_response.py +++ /dev/null @@ -1,417 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/get_content_standards_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class GetContentStandardsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class Exemplar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scenario: Annotated[ - str, - Field(description='A concrete scenario describing an advertising action or configuration.'), - ] - explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] - - -class AssetAccess10(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess11(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess12(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess10 | AssetAccess11 | AssetAccess12]): - root: Annotated[ - AssetAccess10 | AssetAccess11 | AssetAccess12, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' diff --git a/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_request.py b/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_request.py deleted file mode 100644 index 9e56e864..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_request.py +++ /dev/null @@ -1,655 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/get_media_buy_artifacts_request.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent423(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent423 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class TimeRange(AdCPBaseModel): - start: Annotated[AwareDatetime | None, Field(description='Start of time range (inclusive)')] = ( - None - ) - end: Annotated[AwareDatetime | None, Field(description='End of time range (exclusive)')] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, - Field(description='Maximum number of artifacts to return per page', ge=1, le=10000), - ] = 1000 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetMediaBuyArtifactsRequest(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account17 | None, - Field( - description='Filter artifacts to a specific account. When omitted, returns artifacts across all accessible accounts.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - media_buy_id: Annotated[str, Field(description='Media buy to get artifacts from')] - package_ids: Annotated[ - list[str] | None, - Field(description='Filter to specific packages within the media buy', min_length=1), - ] = None - failures_only: Annotated[ - bool | None, - Field( - description="When true, only return artifacts where the seller's local model returned local_verdict: 'fail'. Useful for auditing false positives. Not useful when the seller does not run a local evaluation model (all verdicts are 'unevaluated')." - ), - ] = False - time_range: Annotated[TimeRange | None, Field(description='Filter to specific time period')] = ( - None - ) - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Uses higher limits than standard pagination because artifact result sets can be very large.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_response.py b/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_response.py deleted file mode 100644 index 8a49d5d9..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/get_media_buy_artifacts_response.py +++ /dev/null @@ -1,383 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/get_media_buy_artifacts_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class GetMediaBuyArtifactsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class AssetAccess13(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess14(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess15(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess13 | AssetAccess14 | AssetAccess15]): - root: Annotated[ - AssetAccess13 | AssetAccess14 | AssetAccess15, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' diff --git a/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_request.py b/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_request.py deleted file mode 100644 index 78e1b4b2..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_request.py +++ /dev/null @@ -1,100 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/list_content_standards_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Channel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class ListContentStandardsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - channels: Annotated[ - list[Channel] | None, Field(description='Filter by channel', min_length=1) - ] = None - languages: Annotated[ - list[str] | None, Field(description='Filter by BCP 47 language tags', min_length=1) - ] = None - countries: Annotated[ - list[str] | None, - Field(description='Filter by ISO 3166-1 alpha-2 country codes', min_length=1), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_response.py b/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_response.py deleted file mode 100644 index 357440b7..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/list_content_standards_response.py +++ /dev/null @@ -1,417 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/list_content_standards_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class ListContentStandardsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class Exemplar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scenario: Annotated[ - str, - Field(description='A concrete scenario describing an advertising action or configuration.'), - ] - explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] - - -class AssetAccess16(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess17(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess18(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess16 | AssetAccess17 | AssetAccess18]): - root: Annotated[ - AssetAccess16 | AssetAccess17 | AssetAccess18, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' diff --git a/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_request.py b/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_request.py deleted file mode 100644 index c5725631..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_request.py +++ /dev/null @@ -1,2708 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/update_content_standards_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Source(StrEnum): - registry = 'registry' - inline = 'inline' - - -class Category(StrEnum): - regulation = 'regulation' - standard = 'standard' - - -class Enforcement(StrEnum): - must = 'must' - should = 'should' - may = 'may' - - -class GovernanceDomain(StrEnum): - campaign = 'campaign' - property = 'property' - creative = 'creative' - content_standards = 'content_standards' - - -class Pass(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://espn.com/nba/story/_/id/12345/lakers-win')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Role(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role1298(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role1298, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent2455(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class DeclaredBy1234(DeclaredBy): - pass - - -class VerifyAgent2456(VerifyAgent): - pass - - -class VerifyAgent2457(VerifyAgent2455): - pass - - -class VerificationItem1228(VerificationItem): - pass - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class DeclaredBy1235(DeclaredBy): - pass - - -class VerifyAgent2458(VerifyAgent): - pass - - -class VerifyAgent2459(VerifyAgent2455): - pass - - -class VerificationItem1229(VerificationItem): - pass - - -class TranscriptSource9(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class DeclaredBy1236(DeclaredBy): - pass - - -class VerifyAgent2460(VerifyAgent): - pass - - -class VerifyAgent2461(VerifyAgent2455): - pass - - -class VerificationItem1230(VerificationItem): - pass - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class DeclaredBy1237(DeclaredBy): - pass - - -class VerifyAgent2462(VerifyAgent): - pass - - -class VerifyAgent2463(VerifyAgent2455): - pass - - -class VerificationItem1231(VerificationItem): - pass - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class Fail(AdCPBaseModel): - type: Annotated[Literal['url'], Field(description='Indicates this is a URL reference')] = 'url' - value: Annotated[ - AnyUrl, - Field( - description="Full URL to a specific page (e.g., 'https://news.example.com/controversial-article')" - ), - ] - language: Annotated[ - str | None, Field(description='BCP 47 language tag for content at this URL') - ] = None - - -class DeclaredBy1238(DeclaredBy): - pass - - -class VerifyAgent2464(VerifyAgent): - pass - - -class VerifyAgent2465(VerifyAgent2455): - pass - - -class VerificationItem1232(VerificationItem): - pass - - -class DeclaredBy1239(DeclaredBy): - pass - - -class VerifyAgent2466(VerifyAgent): - pass - - -class VerifyAgent2467(VerifyAgent2455): - pass - - -class VerificationItem1233(VerificationItem): - pass - - -class DeclaredBy1240(DeclaredBy): - pass - - -class VerifyAgent2468(VerifyAgent): - pass - - -class VerifyAgent2469(VerifyAgent2455): - pass - - -class VerificationItem1234(VerificationItem): - pass - - -class DeclaredBy1241(DeclaredBy): - pass - - -class VerifyAgent2470(VerifyAgent): - pass - - -class VerifyAgent2471(VerifyAgent2455): - pass - - -class VerificationItem1235(VerificationItem): - pass - - -class DeclaredBy1242(DeclaredBy): - pass - - -class VerifyAgent2472(VerifyAgent): - pass - - -class VerifyAgent2473(VerifyAgent2455): - pass - - -class VerificationItem1236(VerificationItem): - pass - - -class Exemplar(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scenario: Annotated[ - str, - Field(description='A concrete scenario describing an advertising action or configuration.'), - ] - explanation: Annotated[str, Field(description='Why this scenario passes or fails the policy.')] - - -class AssetAccess19(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess20(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess21(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess19 | AssetAccess20 | AssetAccess21]): - root: Annotated[ - AssetAccess19 | AssetAccess20 | AssetAccess21, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class Scope(AdCPBaseModel): - countries_all: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes. Standards apply in ALL listed countries (AND logic).', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[MediaChannel] | None, - Field( - description='Advertising channels. Standards apply to ANY of the listed channels (OR logic).', - min_length=1, - ), - ] = None - languages_any: Annotated[ - list[str] | None, - Field( - description="BCP 47 language tags (e.g., 'en', 'de', 'fr'). Standards apply to content in ANY of these languages (OR logic). Content in unlisted languages is not covered by these standards.", - min_length=1, - ), - ] = None - description: Annotated[ - str | None, Field(description='Human-readable description of this scope') - ] = None - - -class Exemplars(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - pass_: Annotated[ - list[Exemplar] | None, - Field(alias='pass', description='Scenarios that comply with this policy.'), - ] = None - fail: Annotated[ - list[Exemplar] | None, Field(description='Scenarios that violate this policy.') - ] = None - - -class Policy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - policy_id: Annotated[ - str, - Field( - description='Unique identifier for this policy. Registry-published ids are canonical (e.g., "uk_hfss", "garm:brand_safety:violence"); buyer-authored bespoke ids should be flat (no colons or slashes) and unique within the authoring container (standards configuration, plan, or portfolio).' - ), - ] - source: Annotated[ - Source | None, - Field( - description="Origin of this policy. 'registry' = published to the shared AdCP policy registry with full regulatory metadata. 'inline' = authored bespoke for a specific standards configuration, plan, or portfolio. Defaults to 'inline'. Governance agents MUST set 'registry' when publishing to the registry. Within AdCP *task* payloads (every `$ref` to this schema in a request or response), the field is always 'inline' — registry entries are served by the policy registry API, not embedded in task traffic. The x-entity annotation on `policy_id` assumes the task-payload invariant; if a future task schema adopts registry-publishing, split the annotation accordingly (see issue #2685)." - ), - ] = Source.inline - version: Annotated[ - str | None, - Field( - description='Semver version string (e.g., "1.0.0"). Incremented when policy content changes. Optional for inline bespoke policies — defaults to "1.0.0". SHOULD be provided for registry-published policies.' - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name (e.g., "UK HFSS Restrictions"). Optional for inline bespoke policies — servers MAY default to policy_id.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Brief summary of what this policy covers.', max_length=500) - ] = None - category: Annotated[ - Category | None, - Field( - description='The nature of the obligation: regulation (legal requirement) or standard (best practice). Optional for inline bespoke policies — defaults to "standard".', - title='Policy Category', - ), - ] = None - enforcement: Annotated[ - Enforcement, - Field( - description='How governance agents treat violations. Regulations are typically "must"; standards are typically "should".', - title='Policy Enforcement Level', - ), - ] - requires_human_review: Annotated[ - bool | None, - Field( - description='When true, plans subject to this policy MUST set plan.human_review_required = true. Use for policies that mandate human oversight of decisions affecting data subjects — e.g., GDPR Article 22 (solely automated decisions with legal or similarly significant effects) and EU AI Act Annex III high-risk categories (credit, insurance pricing, recruitment, housing allocation). Governance agents MUST escalate any plan action whose resolved policies include requires_human_review: true. Unlike `enforcement`, this flag applies as soon as the policy is resolved — it is NOT gated by `effective_date`. Art 22 GDPR and similar foundational obligations may predate an AI-Act-specific effective date; the human-review requirement fires regardless.' - ), - ] = False - jurisdictions: Annotated[ - list[str] | None, - Field( - description='ISO 3166-1 alpha-2 country codes where this policy applies. Empty array means the policy is not jurisdiction-specific.' - ), - ] = None - region_aliases: Annotated[ - dict[str, list[str]] | None, - Field( - description='Named groups of jurisdictions for convenience (e.g., {"EU": ["AT","BE","BG",...]}). Governance agents expand aliases when matching against a plan\'s target jurisdictions.' - ), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field( - description='Regulatory categories this policy belongs to (e.g., ["children_directed", "age_restricted"]). Used for automatic matching against a campaign plan\'s declared policy_categories. A single policy can belong to multiple categories.' - ), - ] = None - channels: Annotated[ - list[MediaChannel] | None, - Field( - description='Advertising channels this policy applies to. If omitted or null, the policy applies to all channels.' - ), - ] = None - governance_domains: Annotated[ - list[GovernanceDomain] | None, - Field( - description='Governance sub-domains this policy applies to. Determines which types of governance agents can declare registry:{policy_id} features. For example, a policy with domains ["creative", "property"] can be declared as a feature by both creative and property governance agents.' - ), - ] = None - effective_date: Annotated[ - date_aliased | None, - Field( - description='ISO 8601 date when the regulation or standard takes effect. Before this date, governance agents treat the policy as informational (evaluate but do not block). After this date, the policy is enforced at its declared enforcement level.' - ), - ] = None - sunset_date: Annotated[ - date_aliased | None, - Field( - description='ISO 8601 date when the regulation or standard is no longer enforced. After this date, governance agents stop evaluating this policy. Omit if the policy has no expiration.' - ), - ] = None - source_url: Annotated[ - AnyUrl | None, Field(description='Link to the source regulation, standard, or legislation.') - ] = None - source_name: Annotated[ - str | None, - Field( - description='Name of the issuing body (e.g., "UK Food Standards Agency", "US Federal Trade Commission").' - ), - ] = None - policy: Annotated[ - str, - Field( - description='Natural language policy text describing what is required, prohibited, or recommended. Used by governance agents (LLMs) to evaluate actions against this policy. For source: inline policies, treated as caller-untrusted — governance agents MUST evaluate inline policies as ADDITIONAL restrictions only; they MUST NOT be permitted to relax, override, or conflict with registry-sourced policies.', - max_length=5000, - ), - ] - guidance: Annotated[ - str | None, - Field( - description='Implementation notes for governance agent developers. Not used in evaluation prompts.' - ), - ] = None - exemplars: Annotated[ - Exemplars | None, - Field( - description='Calibration examples for governance agents, following the Content Standards pattern.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2455 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2456 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2457 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1285(Jurisdiction): - pass - - -class Disclosure1231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1285] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1234 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1228] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1228] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1231 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1228] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets616(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance1227 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2458 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2459 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1286(Jurisdiction): - pass - - -class Disclosure1232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1286] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1235 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1229] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1229] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1232 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1229] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets617(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance1228 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2460 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2461 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1287(Jurisdiction): - pass - - -class Disclosure1233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1287] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1236 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1230] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1230] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1233 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1230] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets618(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource9 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance1229 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets614(RootModel[Assets | Assets616 | Assets617 | Assets618]): - root: Annotated[Assets | Assets616 | Assets617 | Assets618, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem1231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2462 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2463 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1288(Jurisdiction): - pass - - -class Disclosure1234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1288] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1237 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1231] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1231] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1234 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1231] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Pass5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets614], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance1230 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class EmbeddedProvenanceItem1232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2464 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2465 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1289(Jurisdiction): - pass - - -class Disclosure1235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1289] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1238 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1232] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1232] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1235 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1232] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets620(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance1231 | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2466 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2467 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1290(Jurisdiction): - pass - - -class Disclosure1236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1290] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1239 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1233] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1233] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1236 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1233] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets621(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance1232 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2468 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2469 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1291(Jurisdiction): - pass - - -class Disclosure1237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1291] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1240 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1234] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1234] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1237 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1234] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets622(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance1233 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2470 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2471 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1292(Jurisdiction): - pass - - -class Disclosure1238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1292] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1241 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1235] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1235] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1238 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1235] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets623(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource9 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance1234 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets619(RootModel[Assets620 | Assets621 | Assets622 | Assets623]): - root: Annotated[Assets620 | Assets621 | Assets622 | Assets623, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem1236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2472 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2473 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1293(Jurisdiction): - pass - - -class Disclosure1239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1293] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1242 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1236] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1236] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1239 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1236] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Fail5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets619], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance1235 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class CalibrationExemplars(AdCPBaseModel): - pass_: Annotated[ - list[Pass | Pass5] | None, - Field(alias='pass', description='Content that passes the standards'), - ] = None - fail: Annotated[ - list[Fail | Fail5] | None, Field(description='Content that fails the standards') - ] = None - - -class UpdateContentStandardsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - standards_id: Annotated[str, Field(description='ID of the standards configuration to update')] - scope: Annotated[ - Scope | None, - Field(description='Updated scope for where this standards configuration applies'), - ] = None - registry_policy_ids: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs to use as the evaluation basis. When provided, the agent resolves policies from the registry and uses their policy text and exemplars as the evaluation criteria.' - ), - ] = None - policies: Annotated[ - list[Policy] | None, - Field( - description='Updated bespoke policies for this content-standards configuration, using the same shape as registry entries. Replaces the existing policies array; use stable policy_ids to track policies across versions. Combines with registry_policy_ids. Bespoke policy_ids MUST be flat (no colons/slashes).', - min_length=1, - ), - ] = None - calibration_exemplars: Annotated[ - CalibrationExemplars | None, - Field( - description='Updated training/test set to calibrate policy interpretation. Use URL references for pages to be fetched and analyzed, or full artifacts for pre-extracted content.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_response.py b/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_response.py deleted file mode 100644 index 76a5a7b3..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/update_content_standards_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/update_content_standards_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class UpdateContentStandardsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_request.py b/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_request.py deleted file mode 100644 index 921c6886..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_request.py +++ /dev/null @@ -1,1448 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/validate_content_delivery_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Role(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role1315(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role1315, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent2483(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class DeclaredBy1248(DeclaredBy): - pass - - -class VerifyAgent2484(VerifyAgent): - pass - - -class VerifyAgent2485(VerifyAgent2483): - pass - - -class VerificationItem1242(VerificationItem): - pass - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class DeclaredBy1249(DeclaredBy): - pass - - -class VerifyAgent2486(VerifyAgent): - pass - - -class VerifyAgent2487(VerifyAgent2483): - pass - - -class VerificationItem1243(VerificationItem): - pass - - -class TranscriptSource13(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class DeclaredBy1250(DeclaredBy): - pass - - -class VerifyAgent2488(VerifyAgent): - pass - - -class VerifyAgent2489(VerifyAgent2483): - pass - - -class VerificationItem1244(VerificationItem): - pass - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class DeclaredBy1251(DeclaredBy): - pass - - -class VerifyAgent2490(VerifyAgent): - pass - - -class VerifyAgent2491(VerifyAgent2483): - pass - - -class VerificationItem1245(VerificationItem): - pass - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class BrandContext(AdCPBaseModel): - brand_id: Annotated[str | None, Field(description='Brand identifier')] = None - sku_id: Annotated[str | None, Field(description='Product/SKU identifier if applicable')] = None - - -class AssetAccess22(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess23(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess24(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess22 | AssetAccess23 | AssetAccess24]): - root: Annotated[ - AssetAccess22 | AssetAccess23 | AssetAccess24, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2483 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2484 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2485 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1299(Jurisdiction): - pass - - -class Disclosure1245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1299] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1248 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1242] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1242] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1245 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1242] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets626(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance1241 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2486 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2487 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1300(Jurisdiction): - pass - - -class Disclosure1246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1300] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1249 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1243] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1243] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1246 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1243] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets627(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance1242 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2488 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2489 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1301(Jurisdiction): - pass - - -class Disclosure1247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1301] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1250 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1244] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1244] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1247 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1244] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets628(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource13 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance1243 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets624(RootModel[Assets | Assets626 | Assets627 | Assets628]): - root: Annotated[Assets | Assets626 | Assets627 | Assets628, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem1245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2490 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2491 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1302(Jurisdiction): - pass - - -class Disclosure1248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1302] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1251 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1245] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1245] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1248 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1245] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Artifact(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets624], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance1244 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class Record(AdCPBaseModel): - record_id: Annotated[str, Field(description='Unique identifier for this delivery record')] - media_buy_id: Annotated[ - str | None, - Field(description='Media buy this record belongs to (when batching across multiple buys)'), - ] = None - timestamp: Annotated[AwareDatetime | None, Field(description='When the delivery occurred')] = ( - None - ) - artifact: Annotated[ - Artifact, Field(description='Artifact where ad was delivered', title='Artifact') - ] - country: Annotated[ - str | None, Field(description='ISO 3166-1 alpha-2 country code where delivery occurred') - ] = None - channel: Annotated[ - str | None, Field(description='Channel type (e.g., display, video, audio, social)') - ] = None - brand_context: Annotated[ - BrandContext | None, - Field( - description='Brand information for policy evaluation. Schema TBD - placeholder for brand identifiers.' - ), - ] = None - - -class ValidateContentDeliveryRequest(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - standards_id: Annotated[str, Field(description='Standards configuration to validate against')] - records: Annotated[ - list[Record], - Field( - description='Delivery records to validate (max 10,000)', max_length=10000, min_length=1 - ), - ] - feature_ids: Annotated[ - list[str] | None, - Field(description='Specific features to evaluate (defaults to all)', min_length=1), - ] = None - include_passed: Annotated[ - bool | None, Field(description='Include passed records in results') - ] = True - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_response.py b/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_response.py deleted file mode 100644 index baaf784e..00000000 --- a/src/adcp/types/generated_poc/bundled/content_standards/validate_content_delivery_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/content_standards/validate_content_delivery_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class ValidateContentDeliveryResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/core/__init__.py b/src/adcp/types/generated_poc/bundled/core/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/core/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/core/tasks_get_request.py b/src/adcp/types/generated_poc/bundled/core/tasks_get_request.py deleted file mode 100644 index 4b008609..00000000 --- a/src/adcp/types/generated_poc/bundled/core/tasks_get_request.py +++ /dev/null @@ -1,630 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/core/tasks_get_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1779(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1779 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account51(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class TasksGetRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] - account: Annotated[ - Account | Account51 | None, - Field( - description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - include_history: Annotated[ - bool | None, - Field( - description='Include full conversation history for this task (may increase response size)' - ), - ] = False - include_result: Annotated[ - bool | None, - Field( - description="Include the task's result payload when status is completed. Defaults to false for lightweight status-only polls. When true, sellers MUST include result on the response when status is completed." - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/core/tasks_get_response.py b/src/adcp/types/generated_poc/bundled/core/tasks_get_response.py deleted file mode 100644 index cd8aaa71..00000000 --- a/src/adcp/types/generated_poc/bundled/core/tasks_get_response.py +++ /dev/null @@ -1,126474 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/core/tasks_get_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Progress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - - -class Type(StrEnum): - request = 'request' - response = 'response' - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] - type: Annotated[ - Type, Field(description='Whether this was a request from client or response from server') - ] - data: Annotated[dict[str, Any], Field(description='The full request or response payload')] - - -class Issue89(Issue): - pass - - -class AdcpError46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue89] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class PublisherDomain(RootModel[str]): - root: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class PublisherProperty121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class PublisherProperty122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PropertyTag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class PublisherProperty123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -class PublisherProperty124(AdCPBaseModel): - pass - - -class PublisherProperty125(PublisherProperty121, PublisherProperty124): - pass - - -class PublisherProperty126(PublisherProperty122, PublisherProperty124): - pass - - -class PublisherProperty127(PublisherProperty123, PublisherProperty124): - pass - - -class PublisherProperty( - RootModel[PublisherProperty125 | PublisherProperty126 | PublisherProperty127] -): - root: PublisherProperty125 | PublisherProperty126 | PublisherProperty127 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class V1FormatRefItem(FormatId): - pass - - -class FormatSchema(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class PlatformExtension(FormatSchema): - pass - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(RootModel[str]): - root: Annotated[ - str, - Field( - examples=[ - 'list_creatives', - 'sync_creatives', - 'create_media_buy', - 'get_media_buy_delivery', - 'get_creative_delivery', - ], - min_length=1, - ), - ] - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class RequiredConnection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[int, Field(ge=1)] - height: Annotated[int, Field(ge=1)] - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RequiredConnection121(RequiredConnection): - pass - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class RequiredConnection122(RequiredConnection): - pass - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class RequiredConnection123(RequiredConnection): - pass - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class RequiredConnection124(RequiredConnection): - pass - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(RootModel[int]): - root: Annotated[int, Field(ge=0)] - - -class VideoCodec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class AudioCodec(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(RootModel[int]): - root: Annotated[int, Field(ge=1)] - - -class CompanionBannerHeight(CompanionBannerWidth): - pass - - -class RequiredConnection125(RequiredConnection): - pass - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class RequiredConnection126(RequiredConnection): - pass - - -class AudioCodec25(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class AudioSampleRate(CompanionBannerWidth): - pass - - -class AudioChannel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class RequiredConnection127(RequiredConnection): - pass - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class RequiredConnection128(RequiredConnection): - pass - - -class SupportedCatalogType(StrEnum): - product = 'product' - store = 'store' - offering = 'offering' - hotel = 'hotel' - flight = 'flight' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - job = 'job' - inventory = 'inventory' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class RequiredConnection129(RequiredConnection): - pass - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat23(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource48(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class RequiredConnection130(RequiredConnection): - pass - - -class RequiredConnection131(RequiredConnection): - pass - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class Kind(StrEnum): - publisher_ref = 'publisher_ref' - seller_inline = 'seller_inline' - - -class Mode(StrEnum): - targetable = 'targetable' - included = 'included' - - -class RequiredConnection132(RequiredConnection): - pass - - -class RequiredConnection133(RequiredConnection): - pass - - -class RequiredConnection134(RequiredConnection): - pass - - -class RequiredConnection135(RequiredConnection): - pass - - -class RequiredConnection136(RequiredConnection): - pass - - -class RequiredConnection137(RequiredConnection): - pass - - -class RequiredConnection138(RequiredConnection): - pass - - -class RequiredConnection139(RequiredConnection): - pass - - -class RequiredConnection140(RequiredConnection): - pass - - -class RequiredConnection141(RequiredConnection): - pass - - -class RequiredConnection142(RequiredConnection): - pass - - -class RequiredConnection143(RequiredConnection): - pass - - -class PriceGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - p25: Annotated[ - float | None, Field(description='25th percentile of recent winning bids', ge=0.0) - ] = None - p50: Annotated[float | None, Field(description='Median of recent winning bids', ge=0.0)] = None - p75: Annotated[ - float | None, Field(description='75th percentile of recent winning bids', ge=0.0) - ] = None - p90: Annotated[ - float | None, Field(description='90th percentile of recent winning bids', ge=0.0) - ] = None - - -class ViewThreshold(RootModel[float]): - root: Annotated[ - float, - Field( - description='Percentage completion threshold (0.0 to 1.0, e.g., 0.5 = 50%)', - ge=0.0, - le=1.0, - ), - ] - - -class ViewThreshold11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - duration_seconds: Annotated[int, Field(description='Seconds of viewing required', ge=1)] - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold | ViewThreshold11 - - -class Parameters21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['dooh'], Field(description='Discriminator identifying this as DOOH parameters') - ] = 'dooh' - sov_percentage: Annotated[ - float | None, - Field(description='Guaranteed share of voice as a percentage (0-100)', ge=0.0, le=100.0), - ] = None - loop_duration_seconds: Annotated[ - int | None, Field(description='Duration of the ad loop rotation in seconds', ge=1) - ] = None - min_plays_per_hour: Annotated[ - int | None, Field(description='Minimum number of plays per hour guaranteed', ge=1) - ] = None - venue_package: Annotated[ - str | None, Field(description='Named collection of screens included in this buy') - ] = None - duration_hours: Annotated[ - float | None, - Field( - description='Duration of the DOOH slot in hours (e.g., 24 for a full-day takeover)', - ge=0.0, - ), - ] = None - daypart: Annotated[ - str | None, - Field(description='Named daypart for this slot (e.g., morning_commute, evening_rush)'), - ] = None - estimated_impressions: Annotated[ - int | None, - Field( - description='Estimated audience impressions for this slot (informational, not a delivery guarantee)', - ge=0, - ), - ] = None - - -class TimeUnit(StrEnum): - hour = 'hour' - day = 'day' - week = 'week' - month = 'month' - - -class Parameters22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - time_unit: Annotated[ - TimeUnit, - Field( - description='The time unit for pricing. Total cost = fixed_price × number of time_units in the campaign flight.' - ), - ] - min_duration: Annotated[ - int | None, Field(description='Minimum booking duration in time_units', ge=1) - ] = None - max_duration: Annotated[ - int | None, - Field( - description='Maximum booking duration in time_units. Must be >= min_duration when both are present.', - ge=1, - ), - ] = None - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class Dimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' - placement_ref: Annotated[ - PlacementRef, - Field( - description="Structured placement reference for this forecast row. References an entry from the product's placements array.", - title='Placement Reference', - ), - ] - placement_name: Annotated[ - str | None, - Field( - description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' - ), - ] = None - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Presence(StrEnum): - present = 'present' - absent = 'absent' - - -class Dimensions66(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class AudienceSize(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) - ] = None - - -class Reach(AudienceSize): - pass - - -class Frequency(AudienceSize): - pass - - -class Impressions(AudienceSize): - pass - - -class Clicks(AudienceSize): - pass - - -class Spend(AudienceSize): - pass - - -class Views(AudienceSize): - pass - - -class CompletedViews(AudienceSize): - pass - - -class Grps(AudienceSize): - pass - - -class Engagements(AudienceSize): - pass - - -class Follows(AudienceSize): - pass - - -class Saves(AudienceSize): - pass - - -class ProfileVisits(AudienceSize): - pass - - -class MeasuredImpressions(AudienceSize): - pass - - -class Downloads(AudienceSize): - pass - - -class Plays(AudienceSize): - pass - - -class CoverageRate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0) - ] = None - - -class Metrics(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate | None, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1781(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class MeasurableImpressions(AudienceSize): - pass - - -class ViewableImpressions(AudienceSize): - pass - - -class ViewableRate(CoverageRate): - pass - - -class ViewedSeconds(AudienceSize): - pass - - -class DeclaredBy897(DeclaredBy): - pass - - -class VerifyAgent1782(VerifyAgent): - pass - - -class VerifyAgent1783(VerifyAgent1781): - pass - - -class VerificationItem891(VerificationItem): - pass - - -class Value(AudienceSize): - pass - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class OutcomeMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy898(DeclaredBy): - pass - - -class VerifyAgent1784(VerifyAgent): - pass - - -class VerifyAgent1785(VerifyAgent1781): - pass - - -class VerificationItem892(VerificationItem): - pass - - -class DeclaredBy899(DeclaredBy): - pass - - -class VerifyAgent1786(VerifyAgent): - pass - - -class VerifyAgent1787(VerifyAgent1781): - pass - - -class VerificationItem893(VerificationItem): - pass - - -class DeclaredBy900(DeclaredBy): - pass - - -class VerifyAgent1788(VerifyAgent): - pass - - -class VerifyAgent1789(VerifyAgent1781): - pass - - -class VerificationItem894(VerificationItem): - pass - - -class NoticePeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class Type49(StrEnum): - percent_remaining = 'percent_remaining' - full_commitment = 'full_commitment' - fixed_fee = 'fixed_fee' - none = 'none' - - -class CancellationFee(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type49, - Field( - description="Fee calculation method. 'percent_remaining': percentage of remaining uncommitted spend. 'full_commitment': buyer owes the full committed budget regardless of delivery. 'fixed_fee': flat monetary amount. 'none': no financial fee (cancellation with notice is free)." - ), - ] - rate: Annotated[ - float | None, - Field( - description="Fee rate as a decimal proportion of remaining committed spend. Required when type is 'percent_remaining' (e.g., 0.5 means 50% of remaining spend).", - ge=0.0, - le=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Fixed fee amount in the buy's currency. Required when type is 'fixed_fee'.", - ge=0.0, - ), - ] = None - - -class CancellationPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee, Field(description='Fee applied when the notice period is not met.') - ] - - -class Sla(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - response_max: Annotated[ - str | None, - Field( - description='Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration.', - examples=['PT5M', 'PT4H', 'P1D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - completion_max: Annotated[ - str | None, - Field( - description='Maximum time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, approval resolved). ISO 8601 duration.', - examples=['PT1H', 'PT24H', 'P2D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - - -class DeclaredBy901(DeclaredBy): - pass - - -class VerifyAgent1790(VerifyAgent): - pass - - -class VerifyAgent1791(VerifyAgent1781): - pass - - -class VerificationItem895(VerificationItem): - pass - - -class ME(StrEnum): - zip = 'zip' - zip_plus_four = 'zip_plus_four' - - -class GBEnum(StrEnum): - outward = 'outward' - full = 'full' - - -class CAEnum(StrEnum): - fsa = 'fsa' - full = 'full' - - -class PostalArea(AdCPBaseModel): - US: Annotated[list[ME] | None, Field(min_length=1)] = None - GB: Annotated[list[GBEnum] | None, Field(min_length=1)] = None - CA: Annotated[list[CAEnum] | None, Field(min_length=1)] = None - DE: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - CH: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - AT: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - FR: Annotated[list[Literal['code_postal']] | None, Field(min_length=1)] = None - AU: Annotated[list[Literal['postcode']] | None, Field(min_length=1)] = None - BR: Annotated[list[Literal['cep']] | None, Field(min_length=1)] = None - IN: Annotated[list[Literal['pin']] | None, Field(min_length=1)] = None - ZA: Annotated[list[Literal['postal_code']] | None, Field(min_length=1)] = None - us_zip: Annotated[bool | None, Field(deprecated=True)] = None - us_zip_plus_four: Annotated[bool | None, Field(deprecated=True)] = None - gb_outward: Annotated[bool | None, Field(deprecated=True)] = None - gb_full: Annotated[bool | None, Field(deprecated=True)] = None - ca_fsa: Annotated[bool | None, Field(deprecated=True)] = None - ca_full: Annotated[bool | None, Field(deprecated=True)] = None - de_plz: Annotated[bool | None, Field(deprecated=True)] = None - fr_code_postal: Annotated[bool | None, Field(deprecated=True)] = None - au_postcode: Annotated[bool | None, Field(deprecated=True)] = None - ch_plz: Annotated[bool | None, Field(deprecated=True)] = None - at_plz: Annotated[bool | None, Field(deprecated=True)] = None - - -class SupportsGeoBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - -class DateRangeSupport(StrEnum): - date_range = 'date_range' - lifetime_only = 'lifetime_only' - - -class MeasurementWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - window_id: Annotated[ - str, - Field( - description="Identifier for this maturation stage. Standard broadcast values: 'live' (real-time viewers only), 'c3' (live + 3 days time-shifted), 'c7' (live + 7 days time-shifted). Standard values for other channels include 'tentative' (provisional data available quickly), 'final' (post-processing certified data), 'post_ivt' (digital after invalid-traffic filtering), 'post_sivt' (digital after sophisticated-IVT filtering), 'downloads_7d' / 'downloads_30d' (podcast download maturation). Sellers may define custom IDs.", - examples=['live', 'c3', 'c7', 'tentative', 'final', 'post_ivt', 'downloads_30d'], - max_length=50, - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of what this window measures', - examples=[ - 'Live broadcast impressions only', - 'Live plus 7 days of time-shifted viewing', - 'Tentative plays before IVT and fraud-check processing', - 'Final plays after IVT and fraud-check processing', - 'Impressions after sophisticated invalid-traffic filtering', - ], - max_length=500, - ), - ] = None - duration_days: Annotated[ - int, - Field( - description='Number of days of accumulation included in this window before processing begins. For broadcast, this is DVR accumulation (0 = live only, 3 = live + 3 days DVR, 7 = live + 7 days DVR). For channels without an accumulation period (DOOH tentative→final, digital IVT filtering), this is 0 — maturation is entirely vendor processing time captured in expected_availability_days.', - ge=0, - ), - ] - expected_availability_days: Annotated[ - int | None, - Field( - description="Expected number of days after delivery before this window's data is available from the measurement vendor. Captures accumulation time plus vendor processing time. Examples: broadcast C7 from VideoAmp ~22 days (7-day accumulation + ~15-day processing); DOOH tentative plays same-day; DOOH final (post-IVT/fraud-check) ~1 day; digital post-SIVT ~2–3 days.", - ge=0, - ), - ] = None - is_guarantee_basis: Annotated[ - bool | None, - Field( - description="Whether this window is the basis for delivery guarantees, reconciliation, and invoicing. A product typically has one guarantee basis window (e.g., C7 for most US broadcast, post-IVT final for DOOH). Buyers reconcile against the guarantee basis window's final numbers." - ), - ] = None - - -class ProvenanceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - require_digital_source_type: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `digital_source_type` field in their provenance, set to a valid value from the `digital-source-type` enum (not null or absent). Submissions that omit this field are rejected with `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING`. Supports EU AI Act Art. 50 and CA SB 942 compliance workflows where AI disclosure metadata must be present at the protocol level.' - ), - ] = None - require_disclosure_metadata: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `disclosure` object in their provenance with `disclosure.required` set to a boolean value (true or false). When `disclosure.required` is true, at least one entry in `disclosure.jurisdictions` is expected. Submissions that omit `disclosure.required` are rejected with `PROVENANCE_DISCLOSURE_MISSING`.' - ), - ] = None - require_embedded_provenance: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include at least one `embedded_provenance` entry. For pipelines where sidecar metadata is stripped by intermediaries, this ensures provenance data persists through delivery. Submissions that omit `embedded_provenance` are rejected with `PROVENANCE_EMBEDDED_MISSING`.' - ), - ] = None - - -class AcceptedVerifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent. MUST use the `https://` scheme. The seller calls this URL via `get_creative_features` to verify a buyer's claim; the seller has already vetted the endpoint and accepts responsibility for outbound calls to it." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical `feature_id` the seller will request against this agent (e.g., `encypher.markers_present_v2`). When present, the buyer's `verify_agent.feature_id` SHOULD either match this value or be omitted. When absent, the seller selects a feature from the agent's `governance.creative_features` catalog at evaluation time. Resolves the selector ambiguity that would otherwise let two compliant receivers reach different verdicts." - ), - ] = None - providers: Annotated[ - list[str] | None, - Field( - description="Optional `provider` labels this agent verifies (e.g., `['Encypher', 'Digimarc']`). When present, sellers SHOULD only invoke this agent for `embedded_provenance[]` / `watermarks[]` entries whose `provider` field matches one of these labels — letting buyers pre-flight whether their attached evidence is verifiable against the seller's allowlist. When absent, the agent is treated as provider-agnostic.", - min_length=1, - ), - ] = None - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[float, Field(description='Minimum value, inclusive.')] - max: Annotated[float, Field(description='Maximum value, inclusive.')] - - - - - - -class ActivationStatus(StrEnum): - ready = 'ready' - requires_activation = 'requires_activation' - - -class AllowedTargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption236(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption237(PricingOption231, PricingOption236): - pass - - -class PricingOption238(PricingOption232, PricingOption236): - pass - - -class PricingOption239(PricingOption233, PricingOption236): - pass - - -class PricingOption2310(PricingOption234, PricingOption236): - pass - - -class PricingOption2311(PricingOption235, PricingOption236): - pass - - -class PricingOption( - RootModel[ - PricingOption237 - | PricingOption238 - | PricingOption239 - | PricingOption2310 - | PricingOption2311 - ] -): - root: Annotated[ - PricingOption237 - | PricingOption238 - | PricingOption239 - | PricingOption2310 - | PricingOption2311, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ResolutionModel(StrEnum): - direct_targeting = 'direct_targeting' - seller_planned = 'seller_planned' - - -class SelectionMode(StrEnum): - optional = 'optional' - required = 'required' - fixed = 'fixed' - - -class SelectionGroupRule(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - selection_group: Annotated[ - str, - Field( - description='ProductSignalTargetingOption.selection_group value this rule applies to.' - ), - ] - targeting_mode: Annotated[ - AllowedTargetingMode | None, - Field( - description="How options in this selection_group are intended to be used in signal_targeting_groups. 'include' maps to child groups with operator 'any'. 'exclude' maps to child groups with operator 'none'. Omit when options in the group may be used according to each option's allowed_targeting_modes." - ), - ] = None - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Selection behavior for this selection_group. 'required' means at least min_selected_signals, or 1 when omitted. 'fixed' means default_selected options in this group are seller-applied and read-only." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum selected options from this selection_group. If selection_mode is 'required' and omitted, sellers MUST treat the minimum as 1.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, Field(description='Maximum selected options from this selection_group.', ge=1) - ] = None - - -class SignalTargetingRules(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class SupportedMetric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class SupportedViewDuration(RootModel[float]): - root: Annotated[float, Field(gt=0.0)] - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class DeclaredBy902(DeclaredBy): - pass - - -class VerifyAgent1792(VerifyAgent): - pass - - -class VerifyAgent1793(VerifyAgent1781): - pass - - -class VerificationItem896(VerificationItem): - pass - - -class Severity(StrEnum): - error = 'error' - warning = 'warning' - info = 'info' - - -class Issue90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - severity: Annotated[ - Severity, - Field( - description="'error': blocks optimization until resolved. 'warning': optimization works but effectiveness is reduced. 'info': suggestion for improvement." - ), - ] - message: Annotated[ - str, - Field(description='Human/agent-readable description of the issue and how to resolve it.'), - ] - - -class SupportedTarget20(StrEnum): - cost_per = 'cost_per' - per_ad_spend = 'per_ad_spend' - maximize_value = 'maximize_value' - - -class MatchedGtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class CatalogMatch(AdCPBaseModel): - matched_gtins: Annotated[ - list[MatchedGtin] | None, - Field( - description="GTINs from the buyer's catalog that are eligible on this product's inventory. Standard GTIN formats (GTIN-8 through GTIN-14). Only present for product-type catalogs with GTIN matching." - ), - ] = None - matched_ids: Annotated[ - list[str] | None, - Field( - description="Item IDs from the buyer's catalog that matched this product's inventory. The ID type depends on the catalog type and content_id_type (e.g., SKUs for product catalogs, job_ids for job catalogs, offering_ids for offering catalogs)." - ), - ] = None - matched_count: Annotated[ - int | None, - Field(description="Number of catalog items that matched this product's inventory.", ge=0), - ] = None - submitted_count: Annotated[ - int, Field(description="Total catalog items evaluated from the buyer's catalog.", ge=0) - ] - - -class DeclaredBy903(DeclaredBy): - pass - - -class VerifyAgent1794(VerifyAgent): - pass - - -class VerifyAgent1795(VerifyAgent1781): - pass - - -class VerificationItem897(VerificationItem): - pass - - -class DeclaredBy904(DeclaredBy): - pass - - -class VerifyAgent1796(VerifyAgent): - pass - - -class VerifyAgent1797(VerifyAgent1781): - pass - - -class VerificationItem898(VerificationItem): - pass - - -class DeclaredBy905(DeclaredBy): - pass - - -class VerifyAgent1798(VerifyAgent): - pass - - -class VerifyAgent1799(VerifyAgent1781): - pass - - -class VerificationItem899(VerificationItem): - pass - - -class Specification(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[str, Field(max_length=60)] - value: Annotated[str, Field(max_length=200)] - - -class Collection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_ids: Annotated[ - list[str], - Field( - description='Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.', - min_length=1, - ), - ] - - -class AdInventory(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - expected_breaks: Annotated[ - int, Field(description='Number of planned ad breaks in the installment', ge=0) - ] - total_ad_seconds: Annotated[ - int | None, Field(description='Total seconds of ad time across all breaks', ge=0) - ] = None - max_ad_duration_seconds: Annotated[ - int | None, - Field( - description='Maximum duration in seconds for a single ad within a break. Buyers need this to know whether their creative fits.', - ge=1, - ), - ] = None - unplanned_breaks: Annotated[ - bool | None, - Field( - description='Whether ad breaks are dynamic and driven by live conditions (sports timeouts, election coverage). When false, all breaks are pre-defined.' - ), - ] = None - supported_formats: Annotated[ - list[str] | None, - Field( - description="Ad format types supported in breaks (e.g., 'video', 'audio', 'display')" - ), - ] = None - - -class MaterialDeadline(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - stage: Annotated[ - str, - Field( - description="Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", - examples=['draft', 'final'], - ), - ] - due_at: Annotated[ - AwareDatetime, Field(description='When materials for this stage are due (ISO 8601)') - ] - label: Annotated[ - str | None, - Field( - description="What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" - ), - ] = None - - -class Deadlines(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - booking_deadline: Annotated[ - AwareDatetime | None, - Field( - description='Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings.' - ), - ] = None - cancellation_deadline: Annotated[ - AwareDatetime | None, - Field( - description="Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." - ), - ] = None - material_deadlines: Annotated[ - list[MaterialDeadline] | None, - Field( - description="Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", - min_length=1, - ), - ] = None - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class MaterialSubmission(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field(description='HTTPS URL for uploading or submitting physical creative materials'), - ] = None - email: Annotated[ - EmailStr | None, Field(description='Email address for creative material submission') - ] = None - instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for material submission (file naming conventions, shipping address, etc.)', - max_length=2000, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PublisherProperty131(PublisherProperty121): - pass - - -class PublisherProperty132(PublisherProperty122): - pass - - -class PublisherProperty133(PublisherProperty123): - pass - - -class PublisherProperty134(PublisherProperty124): - pass - - -class PublisherProperty135(PublisherProperty131, PublisherProperty134): - pass - - -class PublisherProperty136(PublisherProperty132, PublisherProperty134): - pass - - -class PublisherProperty137(PublisherProperty133, PublisherProperty134): - pass - - -class PublisherProperty13( - RootModel[PublisherProperty135 | PublisherProperty136 | PublisherProperty137] -): - root: PublisherProperty135 | PublisherProperty136 | PublisherProperty137 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection144(RequiredConnection): - pass - - -class RequiredConnection145(RequiredConnection): - pass - - -class RequiredConnection146(RequiredConnection): - pass - - -class RequiredConnection147(RequiredConnection): - pass - - -class RequiredConnection148(RequiredConnection): - pass - - -class RequiredConnection149(RequiredConnection): - pass - - -class RequiredConnection150(RequiredConnection): - pass - - -class RequiredConnection151(RequiredConnection): - pass - - -class RequiredConnection152(RequiredConnection): - pass - - -class RequiredConnection153(RequiredConnection): - pass - - -class RequiredConnection154(RequiredConnection): - pass - - -class RequiredConnection155(RequiredConnection): - pass - - -class RequiredConnection156(RequiredConnection): - pass - - -class RequiredConnection157(RequiredConnection): - pass - - -class RequiredConnection158(RequiredConnection): - pass - - -class RequiredConnection159(RequiredConnection): - pass - - -class RequiredConnection160(RequiredConnection): - pass - - -class RequiredConnection161(RequiredConnection): - pass - - -class RequiredConnection162(RequiredConnection): - pass - - -class RequiredConnection163(RequiredConnection): - pass - - -class RequiredConnection164(RequiredConnection): - pass - - -class RequiredConnection165(RequiredConnection): - pass - - -class RequiredConnection166(RequiredConnection): - pass - - -class RequiredConnection167(RequiredConnection): - pass - - -class ViewThreshold12(ViewThreshold): - pass - - -class ViewThreshold13(ViewThreshold11): - pass - - -class Parameters23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold12 | ViewThreshold13 - - -class Parameters25(Parameters21): - pass - - -class Parameters26(Parameters22): - pass - - -class Dimensions68(Dimensions): - pass - - - - -class Dimensions72(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics11(Metrics): - pass - - -class DeclaredBy906(DeclaredBy): - pass - - -class VerifyAgent1800(VerifyAgent): - pass - - -class VerifyAgent1801(VerifyAgent1781): - pass - - -class VerificationItem900(VerificationItem): - pass - - -class DeclaredBy907(DeclaredBy): - pass - - -class VerifyAgent1802(VerifyAgent): - pass - - -class VerifyAgent1803(VerifyAgent1781): - pass - - -class VerificationItem901(VerificationItem): - pass - - -class Window11(Window): - pass - - -class OutcomeMeasurement5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window11 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy908(DeclaredBy): - pass - - -class VerifyAgent1804(VerifyAgent): - pass - - -class VerifyAgent1805(VerifyAgent1781): - pass - - -class VerificationItem902(VerificationItem): - pass - - -class DeclaredBy909(DeclaredBy): - pass - - -class VerifyAgent1806(VerifyAgent): - pass - - -class VerifyAgent1807(VerifyAgent1781): - pass - - -class VerificationItem903(VerificationItem): - pass - - -class DeclaredBy910(DeclaredBy): - pass - - -class VerifyAgent1808(VerifyAgent): - pass - - -class VerifyAgent1809(VerifyAgent1781): - pass - - -class VerificationItem904(VerificationItem): - pass - - -class NoticePeriod5(NoticePeriod): - pass - - -class CancellationFee6(CancellationFee): - pass - - -class CancellationPolicy6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod5, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee6, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy911(DeclaredBy): - pass - - -class VerifyAgent1810(VerifyAgent): - pass - - -class VerifyAgent1811(VerifyAgent1781): - pass - - -class VerificationItem905(VerificationItem): - pass - - -class PostalArea7(PostalArea): - pass - - -class SupportsGeoBreakdown5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea7 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption241(PricingOption231): - pass - - -class PricingOption242(PricingOption232): - pass - - -class PricingOption243(PricingOption233): - pass - - -class PricingOption244(PricingOption234): - pass - - -class PricingOption245(PricingOption235): - pass - - -class PricingOption246(PricingOption236): - pass - - -class PricingOption247(PricingOption241, PricingOption246): - pass - - -class PricingOption248(PricingOption242, PricingOption246): - pass - - -class PricingOption249(PricingOption243, PricingOption246): - pass - - -class PricingOption2410(PricingOption244, PricingOption246): - pass - - -class PricingOption2411(PricingOption245, PricingOption246): - pass - - -class PricingOption24( - RootModel[ - PricingOption247 - | PricingOption248 - | PricingOption249 - | PricingOption2410 - | PricingOption2411 - ] -): - root: Annotated[ - PricingOption247 - | PricingOption248 - | PricingOption249 - | PricingOption2410 - | PricingOption2411, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule5(SelectionGroupRule): - pass - - -class SignalTargetingRules5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule5] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy912(DeclaredBy): - pass - - -class VerifyAgent1812(VerifyAgent): - pass - - -class VerifyAgent1813(VerifyAgent1781): - pass - - -class VerificationItem906(VerificationItem): - pass - - -class Issue91(Issue90): - pass - - -class CatalogMatch6(CatalogMatch): - pass - - -class DeclaredBy913(DeclaredBy): - pass - - -class VerifyAgent1814(VerifyAgent): - pass - - -class VerifyAgent1815(VerifyAgent1781): - pass - - -class VerificationItem907(VerificationItem): - pass - - -class DeclaredBy914(DeclaredBy): - pass - - -class VerifyAgent1816(VerifyAgent): - pass - - -class VerifyAgent1817(VerifyAgent1781): - pass - - -class VerificationItem908(VerificationItem): - pass - - -class DeclaredBy915(DeclaredBy): - pass - - -class VerifyAgent1818(VerifyAgent): - pass - - -class VerifyAgent1819(VerifyAgent1781): - pass - - -class VerificationItem909(VerificationItem): - pass - - -class Deadlines5(Deadlines): - pass - - -class Extensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - extends: Annotated[ - str, - Field( - description='Canonical concept this extension extends (e.g., `tracking`, `cta_vocabulary`, `destinations`, `placement`).' - ), - ] - fields: Annotated[ - dict[str, Any], - Field( - description='JSON Schema fragment declaring the additional fields this extension contributes.' - ), - ] - version: Annotated[ - str | None, - Field( - description='Semantic version of the extension definition. Distinct from the digest — version is human-readable; digest is the integrity check.' - ), - ] = None - description: str | None = None - - -class Dimensions74(Dimensions): - pass - - - - -class Dimensions78(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics12(Metrics): - pass - - -class DeclaredBy916(DeclaredBy): - pass - - -class VerifyAgent1820(VerifyAgent): - pass - - -class VerifyAgent1821(VerifyAgent1781): - pass - - -class VerificationItem910(VerificationItem): - pass - - -class DeclaredBy917(DeclaredBy): - pass - - -class VerifyAgent1822(VerifyAgent): - pass - - -class VerifyAgent1823(VerifyAgent1781): - pass - - -class VerificationItem911(VerificationItem): - pass - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - - -class TotalBudget(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[ - str, Field(description='ISO 4217 currency code', max_length=3, min_length=3) - ] - - -class PaymentTerms4(StrEnum): - net_30 = 'net_30' - net_60 = 'net_60' - net_90 = 'net_90' - prepaid = 'prepaid' - due_on_receipt = 'due_on_receipt' - - -class Terms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - advertiser: Annotated[ - str | None, Field(description='Advertiser name or identifier', max_length=500) - ] = None - publisher: Annotated[ - str | None, Field(description='Publisher name or identifier', max_length=500) - ] = None - total_budget: Annotated[TotalBudget | None, Field(description='Total committed budget')] = None - flight_start: Annotated[AwareDatetime | None, Field(description='Campaign start date')] = None - flight_end: Annotated[AwareDatetime | None, Field(description='Campaign end date')] = None - payment_terms: Annotated[PaymentTerms4 | None, Field(description='Payment terms')] = None - - -class InsertionOrder(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, - Field( - description='Unique identifier for this insertion order. Referenced by io_acceptance on create_media_buy.', - max_length=255, - ), - ] - terms: Annotated[ - Terms | None, - Field( - description='Summary fields echoed from the committed proposal for agent verification. Buyer agents use these to confirm the IO matches what was negotiated before a human signs. These are read-only summaries, not negotiation surfaces — deal terms live on products and packages.' - ), - ] = None - terms_url: Annotated[ - AnyUrl | None, - Field( - description='URL to a human-readable document containing the full insertion order terms' - ), - ] = None - signing_url: Annotated[ - AnyUrl | None, - Field( - description='URL to an electronic signing service (e.g., DocuSign) for human signature workflows. When present, a human must sign before the buyer agent can proceed with create_media_buy.' - ), - ] = None - requires_signature: Annotated[ - bool, - Field( - description='Whether the buyer must accept this IO before creating a media buy. When true, create_media_buy requires an io_acceptance referencing this io_id.' - ), - ] - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[float | None, Field(description='Minimum recommended budget', ge=0.0)] = None - recommended: Annotated[ - float | None, Field(description='Recommended budget for optimal performance', ge=0.0) - ] = None - max: Annotated[ - float | None, Field(description='Maximum budget before diminishing returns', ge=0.0) - ] = None - currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None - - -class Dimensions80(Dimensions): - pass - - - - -class Dimensions84(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics13(Metrics): - pass - - -class DeclaredBy918(DeclaredBy): - pass - - -class VerifyAgent1824(VerifyAgent): - pass - - -class VerifyAgent1825(VerifyAgent1781): - pass - - -class VerificationItem912(VerificationItem): - pass - - -class DeclaredBy919(DeclaredBy): - pass - - -class VerifyAgent1826(VerifyAgent): - pass - - -class VerifyAgent1827(VerifyAgent1781): - pass - - -class VerificationItem913(VerificationItem): - pass - - -class Issue92(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue92] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status263(StrEnum): - applied = 'applied' - partial = 'partial' - unable = 'unable' - - -class RefinementApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field(description="Echoes scope 'request' from the corresponding refine entry."), - ] = 'request' - status: Annotated[ - Status263, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['product'], - Field(description="Echoes scope 'product' from the corresponding refine entry."), - ] = 'product' - product_id: Annotated[ - str, Field(description='Echoes product_id from the corresponding refine entry.') - ] - status: Annotated[ - Status263, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], - Field(description="Echoes scope 'proposal' from the corresponding refine entry."), - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Echoes proposal_id from the corresponding refine entry.') - ] - status: Annotated[ - Status263, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied8(RootModel[RefinementApplied | RefinementApplied10 | RefinementApplied11]): - root: Annotated[ - RefinementApplied | RefinementApplied10 | RefinementApplied11, Field(discriminator='scope') - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Scope183(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - proposals = 'proposals' - wholesale_feed = 'wholesale_feed' - - -class EstimatedWait(Window): - pass - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope183, - Field( - description="'products': not all inventory sources were searched. 'pricing': products returned but pricing is absent or unconfirmed. 'forecast': products returned but forecast data is absent. 'proposals': proposals were not generated or are incomplete. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget — symmetric with get_signals' 'wholesale_feed' scope so sellers have a precise way to declare wholesale-incomplete on the products surface." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait | None, - Field( - description='How much additional time would resolve this scope. Allows the buyer to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class Semantics(StrEnum): - only = 'only' - any = 'any' - approximate = 'approximate' - - -class ExcludedBy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - count: Annotated[ - int, - Field( - description='Number of products excluded by this filter, interpreted per the parent `semantics` field.', - ge=0, - ), - ] - values: Annotated[ - list[str | dict[str, Any]] | None, - Field( - description='Optional list of the specific filter values that contributed to exclusions, when meaningful. For `required_metrics`: the metric names that excluded products (strings). For `required_vendor_metrics`: the vendor/metric pin entries (objects). Item shape is filter-specific; the schema admits string OR object items. Buyers without filter-specific knowledge SHOULD treat as opaque.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Optional human-readable note about why this filter narrowed the set (e.g., 'no products in this brief support DV viewability at the requested threshold')." - ), - ] = None - - -class FilterDiagnostics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - semantics: Annotated[ - Semantics | None, - Field( - description="How `excluded_by[*].count` values are computed across multiple filters. `only`: counts products that would have been included if not for THIS filter alone (deterministic; the right value for 'which filter killed my result set' triage — recommended when feasible). `any`: counts products excluded by ANY filter (so multiple filters' counts may overlap and sum to more than `total_candidates`). `approximate`: sellers SHOULD use this when their pipeline can't cleanly attribute exclusions to a single filter. Buyers SHOULD inspect `semantics` before doing arithmetic on counts." - ), - ] = None - total_candidates: Annotated[ - int | None, - Field( - description='Number of products the seller considered before applying `filters`. Baseline for interpreting per-filter exclusion counts. Approximate — sellers MAY return a sampled or capped count when their candidate pool is large. Optional; sellers whose baseline candidate set size is sensitive (revealing market posture or competitive density) MAY omit this while still emitting `excluded_by`.', - ge=0, - ), - ] = None - excluded_by: Annotated[ - dict[str, ExcludedBy] | None, - Field( - description="Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request)." - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class Result956(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, - Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the search process') - ] = None - step_number: Annotated[int | None, Field(description='Current step number (1-indexed)')] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason(StrEnum): - CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' - BUDGET_REQUIRED = 'BUDGET_REQUIRED' - - -class PublisherProperty141(PublisherProperty121): - pass - - -class PublisherProperty142(PublisherProperty122): - pass - - -class PublisherProperty143(PublisherProperty123): - pass - - -class PublisherProperty144(PublisherProperty124): - pass - - -class PublisherProperty145(PublisherProperty141, PublisherProperty144): - pass - - -class PublisherProperty146(PublisherProperty142, PublisherProperty144): - pass - - -class PublisherProperty147(PublisherProperty143, PublisherProperty144): - pass - - -class PublisherProperty14( - RootModel[PublisherProperty145 | PublisherProperty146 | PublisherProperty147] -): - root: PublisherProperty145 | PublisherProperty146 | PublisherProperty147 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection168(RequiredConnection): - pass - - -class RequiredConnection169(RequiredConnection): - pass - - -class RequiredConnection170(RequiredConnection): - pass - - -class RequiredConnection171(RequiredConnection): - pass - - -class RequiredConnection172(RequiredConnection): - pass - - -class RequiredConnection173(RequiredConnection): - pass - - -class RequiredConnection174(RequiredConnection): - pass - - -class RequiredConnection175(RequiredConnection): - pass - - -class RequiredConnection176(RequiredConnection): - pass - - -class RequiredConnection177(RequiredConnection): - pass - - -class RequiredConnection178(RequiredConnection): - pass - - -class RequiredConnection179(RequiredConnection): - pass - - -class RequiredConnection180(RequiredConnection): - pass - - -class RequiredConnection181(RequiredConnection): - pass - - -class RequiredConnection182(RequiredConnection): - pass - - -class RequiredConnection183(RequiredConnection): - pass - - -class RequiredConnection184(RequiredConnection): - pass - - -class RequiredConnection185(RequiredConnection): - pass - - -class RequiredConnection186(RequiredConnection): - pass - - -class RequiredConnection187(RequiredConnection): - pass - - -class RequiredConnection188(RequiredConnection): - pass - - -class RequiredConnection189(RequiredConnection): - pass - - -class RequiredConnection190(RequiredConnection): - pass - - -class RequiredConnection191(RequiredConnection): - pass - - -class ViewThreshold14(ViewThreshold): - pass - - -class ViewThreshold15(ViewThreshold11): - pass - - -class Parameters27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold14 | ViewThreshold15 - - -class Parameters29(Parameters21): - pass - - -class Parameters30(Parameters22): - pass - - -class Dimensions86(Dimensions): - pass - - - - -class Dimensions90(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics14(Metrics): - pass - - -class DeclaredBy920(DeclaredBy): - pass - - -class VerifyAgent1828(VerifyAgent): - pass - - -class VerifyAgent1829(VerifyAgent1781): - pass - - -class VerificationItem914(VerificationItem): - pass - - -class DeclaredBy921(DeclaredBy): - pass - - -class VerifyAgent1830(VerifyAgent): - pass - - -class VerifyAgent1831(VerifyAgent1781): - pass - - -class VerificationItem915(VerificationItem): - pass - - -class Window12(Window): - pass - - -class OutcomeMeasurement6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window12 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy922(DeclaredBy): - pass - - -class VerifyAgent1832(VerifyAgent): - pass - - -class VerifyAgent1833(VerifyAgent1781): - pass - - -class VerificationItem916(VerificationItem): - pass - - -class DeclaredBy923(DeclaredBy): - pass - - -class VerifyAgent1834(VerifyAgent): - pass - - -class VerifyAgent1835(VerifyAgent1781): - pass - - -class VerificationItem917(VerificationItem): - pass - - -class DeclaredBy924(DeclaredBy): - pass - - -class VerifyAgent1836(VerifyAgent): - pass - - -class VerifyAgent1837(VerifyAgent1781): - pass - - -class VerificationItem918(VerificationItem): - pass - - -class NoticePeriod6(NoticePeriod): - pass - - -class CancellationFee7(CancellationFee): - pass - - -class CancellationPolicy7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod6, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee7, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy925(DeclaredBy): - pass - - -class VerifyAgent1838(VerifyAgent): - pass - - -class VerifyAgent1839(VerifyAgent1781): - pass - - -class VerificationItem919(VerificationItem): - pass - - -class PostalArea8(PostalArea): - pass - - -class SupportsGeoBreakdown6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea8 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption251(PricingOption231): - pass - - -class PricingOption252(PricingOption232): - pass - - -class PricingOption253(PricingOption233): - pass - - -class PricingOption254(PricingOption234): - pass - - -class PricingOption255(PricingOption235): - pass - - -class PricingOption256(PricingOption236): - pass - - -class PricingOption257(PricingOption251, PricingOption256): - pass - - -class PricingOption258(PricingOption252, PricingOption256): - pass - - -class PricingOption259(PricingOption253, PricingOption256): - pass - - -class PricingOption2510(PricingOption254, PricingOption256): - pass - - -class PricingOption2511(PricingOption255, PricingOption256): - pass - - -class PricingOption25( - RootModel[ - PricingOption257 - | PricingOption258 - | PricingOption259 - | PricingOption2510 - | PricingOption2511 - ] -): - root: Annotated[ - PricingOption257 - | PricingOption258 - | PricingOption259 - | PricingOption2510 - | PricingOption2511, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule6(SelectionGroupRule): - pass - - -class SignalTargetingRules6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule6] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy926(DeclaredBy): - pass - - -class VerifyAgent1840(VerifyAgent): - pass - - -class VerifyAgent1841(VerifyAgent1781): - pass - - -class VerificationItem920(VerificationItem): - pass - - -class Issue93(Issue90): - pass - - -class CatalogMatch7(CatalogMatch): - pass - - -class DeclaredBy927(DeclaredBy): - pass - - -class VerifyAgent1842(VerifyAgent): - pass - - -class VerifyAgent1843(VerifyAgent1781): - pass - - -class VerificationItem921(VerificationItem): - pass - - -class DeclaredBy928(DeclaredBy): - pass - - -class VerifyAgent1844(VerifyAgent): - pass - - -class VerifyAgent1845(VerifyAgent1781): - pass - - -class VerificationItem922(VerificationItem): - pass - - -class DeclaredBy929(DeclaredBy): - pass - - -class VerifyAgent1846(VerifyAgent): - pass - - -class VerifyAgent1847(VerifyAgent1781): - pass - - -class VerificationItem923(VerificationItem): - pass - - -class Deadlines6(Deadlines): - pass - - -class PublisherProperty151(PublisherProperty121): - pass - - -class PublisherProperty152(PublisherProperty122): - pass - - -class PublisherProperty153(PublisherProperty123): - pass - - -class PublisherProperty154(PublisherProperty124): - pass - - -class PublisherProperty155(PublisherProperty151, PublisherProperty154): - pass - - -class PublisherProperty156(PublisherProperty152, PublisherProperty154): - pass - - -class PublisherProperty157(PublisherProperty153, PublisherProperty154): - pass - - -class PublisherProperty15( - RootModel[PublisherProperty155 | PublisherProperty156 | PublisherProperty157] -): - root: PublisherProperty155 | PublisherProperty156 | PublisherProperty157 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection192(RequiredConnection): - pass - - -class RequiredConnection193(RequiredConnection): - pass - - -class RequiredConnection194(RequiredConnection): - pass - - -class RequiredConnection195(RequiredConnection): - pass - - -class RequiredConnection196(RequiredConnection): - pass - - -class RequiredConnection197(RequiredConnection): - pass - - -class RequiredConnection198(RequiredConnection): - pass - - -class RequiredConnection199(RequiredConnection): - pass - - -class RequiredConnection200(RequiredConnection): - pass - - -class RequiredConnection201(RequiredConnection): - pass - - -class RequiredConnection202(RequiredConnection): - pass - - -class RequiredConnection203(RequiredConnection): - pass - - -class RequiredConnection204(RequiredConnection): - pass - - -class RequiredConnection205(RequiredConnection): - pass - - -class RequiredConnection206(RequiredConnection): - pass - - -class RequiredConnection207(RequiredConnection): - pass - - -class RequiredConnection208(RequiredConnection): - pass - - -class RequiredConnection209(RequiredConnection): - pass - - -class RequiredConnection210(RequiredConnection): - pass - - -class RequiredConnection211(RequiredConnection): - pass - - -class RequiredConnection212(RequiredConnection): - pass - - -class RequiredConnection213(RequiredConnection): - pass - - -class RequiredConnection214(RequiredConnection): - pass - - -class RequiredConnection215(RequiredConnection): - pass - - -class ViewThreshold16(ViewThreshold): - pass - - -class ViewThreshold17(ViewThreshold11): - pass - - -class Parameters31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold16 | ViewThreshold17 - - -class Parameters33(Parameters21): - pass - - -class Parameters34(Parameters22): - pass - - -class Dimensions92(Dimensions): - pass - - - - -class Dimensions96(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics15(Metrics): - pass - - -class DeclaredBy930(DeclaredBy): - pass - - -class VerifyAgent1848(VerifyAgent): - pass - - -class VerifyAgent1849(VerifyAgent1781): - pass - - -class VerificationItem924(VerificationItem): - pass - - -class DeclaredBy931(DeclaredBy): - pass - - -class VerifyAgent1850(VerifyAgent): - pass - - -class VerifyAgent1851(VerifyAgent1781): - pass - - -class VerificationItem925(VerificationItem): - pass - - -class Window13(Window): - pass - - -class OutcomeMeasurement7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window13 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy932(DeclaredBy): - pass - - -class VerifyAgent1852(VerifyAgent): - pass - - -class VerifyAgent1853(VerifyAgent1781): - pass - - -class VerificationItem926(VerificationItem): - pass - - -class DeclaredBy933(DeclaredBy): - pass - - -class VerifyAgent1854(VerifyAgent): - pass - - -class VerifyAgent1855(VerifyAgent1781): - pass - - -class VerificationItem927(VerificationItem): - pass - - -class DeclaredBy934(DeclaredBy): - pass - - -class VerifyAgent1856(VerifyAgent): - pass - - -class VerifyAgent1857(VerifyAgent1781): - pass - - -class VerificationItem928(VerificationItem): - pass - - -class NoticePeriod7(NoticePeriod): - pass - - -class CancellationFee8(CancellationFee): - pass - - -class CancellationPolicy8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod7, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee8, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy935(DeclaredBy): - pass - - -class VerifyAgent1858(VerifyAgent): - pass - - -class VerifyAgent1859(VerifyAgent1781): - pass - - -class VerificationItem929(VerificationItem): - pass - - -class PostalArea9(PostalArea): - pass - - -class SupportsGeoBreakdown7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea9 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption261(PricingOption231): - pass - - -class PricingOption262(PricingOption232): - pass - - -class PricingOption263(PricingOption233): - pass - - -class PricingOption264(PricingOption234): - pass - - -class PricingOption265(PricingOption235): - pass - - -class PricingOption266(PricingOption236): - pass - - -class PricingOption267(PricingOption261, PricingOption266): - pass - - -class PricingOption268(PricingOption262, PricingOption266): - pass - - -class PricingOption269(PricingOption263, PricingOption266): - pass - - -class PricingOption2610(PricingOption264, PricingOption266): - pass - - -class PricingOption2611(PricingOption265, PricingOption266): - pass - - -class PricingOption26( - RootModel[ - PricingOption267 - | PricingOption268 - | PricingOption269 - | PricingOption2610 - | PricingOption2611 - ] -): - root: Annotated[ - PricingOption267 - | PricingOption268 - | PricingOption269 - | PricingOption2610 - | PricingOption2611, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule7(SelectionGroupRule): - pass - - -class SignalTargetingRules7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule7] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy936(DeclaredBy): - pass - - -class VerifyAgent1860(VerifyAgent): - pass - - -class VerifyAgent1861(VerifyAgent1781): - pass - - -class VerificationItem930(VerificationItem): - pass - - -class Issue94(Issue90): - pass - - -class CatalogMatch8(CatalogMatch): - pass - - -class DeclaredBy937(DeclaredBy): - pass - - -class VerifyAgent1862(VerifyAgent): - pass - - -class VerifyAgent1863(VerifyAgent1781): - pass - - -class VerificationItem931(VerificationItem): - pass - - -class DeclaredBy938(DeclaredBy): - pass - - -class VerifyAgent1864(VerifyAgent): - pass - - -class VerifyAgent1865(VerifyAgent1781): - pass - - -class VerificationItem932(VerificationItem): - pass - - -class DeclaredBy939(DeclaredBy): - pass - - -class VerifyAgent1866(VerifyAgent): - pass - - -class VerifyAgent1867(VerifyAgent1781): - pass - - -class VerificationItem933(VerificationItem): - pass - - -class Deadlines7(Deadlines): - pass - - -class Issue95(Issue): - pass - - -class Error43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue95] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result978(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, Field(description='Estimated completion time for the search') - ] = None - errors: Annotated[ - list[Error43] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue96(Issue): - pass - - -class AdcpError47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue96] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - - - - - - - - - -class SignalType(StrEnum): - marketplace = 'marketplace' - custom = 'custom' - owned = 'owned' - - -class Dimensions98(Dimensions): - pass - - - - -class Dimensions102(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics16(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] - - -class DeclaredBy940(DeclaredBy): - pass - - -class VerifyAgent1868(VerifyAgent): - pass - - -class VerifyAgent1869(VerifyAgent1781): - pass - - -class VerificationItem934(VerificationItem): - pass - - -class DeclaredBy941(DeclaredBy): - pass - - -class VerifyAgent1870(VerifyAgent): - pass - - -class VerifyAgent1871(VerifyAgent1781): - pass - - -class VerificationItem935(VerificationItem): - pass - - -class Kind22(StrEnum): - inventory = 'inventory' - product = 'product' - account = 'account' - custom = 'custom' - - -class DateRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[date_aliased, Field(description='Start date (inclusive), ISO 8601')] - end: Annotated[date_aliased, Field(description='End date (inclusive), ISO 8601')] - - -class Scope232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[Kind22, Field(description='Denominator family for the coverage forecast.')] - label: Annotated[ - str, - Field( - description="Human-readable denominator label, such as 'network price-priority inventory'." - ), - ] - product_id: Annotated[ - str | None, Field(description="Product denominator when kind is 'product'.") - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries included in the denominator, as ISO 3166-1 alpha-2 codes.', - min_length=1, - ), - ] = None - line_item_types: Annotated[ - list[str] | None, - Field( - description='Seller or ad-server line item types included in the denominator.', - min_length=1, - ), - ] = None - date_range: Annotated[ - DateRange | None, - Field( - description='Historical or planned date window used to compute the denominator.', - title='Date Range', - ), - ] = None - - -class BucketSemantics(StrEnum): - exclusive = 'exclusive' - overlapping = 'overlapping' - - -class BucketCompleteness(StrEnum): - complete = 'complete' - partial = 'partial' - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Deployments(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[str, Field(description='Platform identifier for DSPs')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - - -class Deployments8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - -class Deployments6(RootModel[Deployments | Deployments8]): - root: Annotated[ - Deployments | Deployments8, - Field( - description='A signal deployment to a specific deployment target with activation status and key', - discriminator='type', - title='Deployment', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class PricingOption271(PricingOption231): - pass - - -class PricingOption272(PricingOption232): - pass - - -class PricingOption273(PricingOption233): - pass - - -class PricingOption274(PricingOption234): - pass - - -class PricingOption275(PricingOption235): - pass - - -class PricingOption276(PricingOption236): - pass - - -class PricingOption277(PricingOption271, PricingOption276): - pass - - -class PricingOption278(PricingOption272, PricingOption276): - pass - - -class PricingOption279(PricingOption273, PricingOption276): - pass - - -class PricingOption2710(PricingOption274, PricingOption276): - pass - - -class PricingOption2711(PricingOption275, PricingOption276): - pass - - -class PricingOption27( - RootModel[ - PricingOption277 - | PricingOption278 - | PricingOption279 - | PricingOption2710 - | PricingOption2711 - ] -): - root: Annotated[ - PricingOption277 - | PricingOption278 - | PricingOption279 - | PricingOption2710 - | PricingOption2711, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RestrictedAttribute(StrEnum): - racial_ethnic_origin = 'racial_ethnic_origin' - political_opinions = 'political_opinions' - religious_beliefs = 'religious_beliefs' - trade_union_membership = 'trade_union_membership' - health_data = 'health_data' - sex_life_sexual_orientation = 'sex_life_sexual_orientation' - genetic_data = 'genetic_data' - biometric_data = 'biometric_data' - age = 'age' - familial_status = 'familial_status' - - -class Value20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(min_length=1)] - path: str | None = None - modifiers: list[str] | None = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: str - taxonomy_value_id: str - path: str | None = None - modifiers: list[str] | None = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: AnyUrl - version: str | None = None - segtax: Annotated[int | None, Field(ge=1)] = None - etag: str | None = None - values: Annotated[list[Value20], Field(min_length=1)] - value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None - parent_match_behavior: ParentMatchBehavior | None = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: bool | None = None - pre_onboarding_device_expansion: bool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class ConsentBasi(StrEnum): - consent = 'consent' - legitimate_interest = 'legitimate_interest' - contract = 'contract' - legal_obligation = 'legal_obligation' - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type53(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type53 - provider_signed: Annotated[ - bool, - Field( - description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Audience(StrEnum): - buyer = 'buyer' - data_subject = 'data_subject' - regulator = 'regulator' - public = 'public' - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - region: Annotated[ - str | None, - Field( - description='Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.' - ), - ] = None - regulation: Annotated[ - str, - Field(description='Provider-supplied regulation identifier for the disclosure obligation.'), - ] - disclosure_text: Annotated[ - str | None, - Field( - description='Human-readable disclosure text or summary the provider expects buyers or reviewers to see.' - ), - ] = None - disclosure_url: Annotated[ - AnyUrl | None, - Field( - description="Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction." - ), - ] = None - audience: Annotated[ - Audience | None, Field(description='Primary audience for this disclosure entry.') - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: Annotated[ - bool, - Field( - description="The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Jurisdictions where a modeling or AI-use disclosure applies.', min_length=1 - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.', - max_length=2000, - ), - ] = None - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method - seed_source: SeedSource - training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] - ai_act_risk_class: AiActRiskClass - disclosure: Annotated[ - Disclosure | None, - Field( - description='Disclosure requirements and jurisdictional notes for modeled data signals. This schema is intentionally separate from core/provenance.json because creative provenance is about generated content, render guidance, and asset-level chain of custody, while signal modeling disclosure is about data-segment methodology and data-use transparency.', - title='Signal Modeling Disclosure', - ), - ] = None - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[list[Right], Field(min_length=1)] - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - countries: list[Country] | None = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[list[Channel], Field(min_length=1)] - response_sla_days: Annotated[int | None, Field(ge=1, le=90)] = None - ccpa_opt_out_url: AnyUrl | None = None - - -class Issue97(Issue): - pass - - -class Error44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue97] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scope233(StrEnum): - signals = 'signals' - pricing = 'pricing' - wholesale_feed = 'wholesale_feed' - - -class EstimatedWait4(Window): - pass - - -class IncompleteItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope233, - Field( - description="'signals': not all matching signals were returned. 'pricing': signals returned but pricing is absent or unconfirmed. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait4 | None, - Field( - description='How much additional time would resolve this scope. Allows the caller to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class Result982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, - Field( - description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 - ), - ] = None - current_step: Annotated[ - str | None, - Field( - description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the signal discovery process.') - ] = None - step_number: Annotated[int | None, Field(description='Current step number (1-indexed).')] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue98(Issue): - pass - - -class Error45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue98] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the caller uses with tasks/get, and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, - Field(description='Estimated completion time for the signal discovery task.'), - ] = None - errors: Annotated[ - list[Error45] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue99(Issue): - pass - - -class AdcpError48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue99] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class DeclaredBy942(DeclaredBy): - pass - - -class VerifyAgent1872(VerifyAgent): - pass - - -class VerifyAgent1873(VerifyAgent1781): - pass - - -class VerificationItem936(VerificationItem): - pass - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role991(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role991, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class BillingEntity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreditLimit(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL where the human can complete the required action (credit application, legal agreement, add funds).' - ), - ] = None - message: Annotated[str, Field(description="Human-readable description of what's needed.")] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When this setup link expires.') - ] = None - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class Compression(StrEnum): - gzip = 'gzip' - none = 'none' - - -class Contact15(Contact): - pass - - -class InvoiceRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact15] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Gtin(MatchedGtin): - pass - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FormatOptionRefs(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRefs8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class FormatOptionRefs6(RootModel[FormatOptionRefs | FormatOptionRefs8]): - root: Annotated[ - FormatOptionRefs | FormatOptionRefs8, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoCountry(Country): - pass - - -class GeoCountriesExcludeItem(Country): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas51(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System61(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas52(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System61, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System62(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas53(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System62, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country90(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System63(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas54(AdCPBaseModel): - country: Annotated[Country90, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas55(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas56(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas57(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas58(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas59(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System69(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas510(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System69, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude251(GeoPostalAreas51): - pass - - -class GeoPostalAreasExclude252(GeoPostalAreas52): - pass - - -class GeoPostalAreasExclude253(GeoPostalAreas53): - pass - - -class GeoPostalAreasExclude254(GeoPostalAreas54): - pass - - -class GeoPostalAreasExclude255(GeoPostalAreas55): - pass - - -class GeoPostalAreasExclude256(GeoPostalAreas56): - pass - - -class GeoPostalAreasExclude257(GeoPostalAreas57): - pass - - -class GeoPostalAreasExclude258(GeoPostalAreas58): - pass - - -class GeoPostalAreasExclude259(GeoPostalAreas59): - pass - - -class GeoPostalAreasExclude2510(GeoPostalAreas510): - pass - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - - - -class Signal61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - - -class Signal64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal65(Signal61, Signal64): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal66(Signal62, Signal64): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal67(Signal63, Signal64): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal65 | Signal66 | Signal67]): - root: Annotated[ - Signal65 | Signal66 | Signal67, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - - - -class SignalTargeting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting12(RootModel[SignalTargeting | SignalTargeting14 | SignalTargeting15]): - root: Annotated[ - SignalTargeting | SignalTargeting14 | SignalTargeting15, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress(Window): - pass - - -class Window14(Window): - pass - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Type54(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type54, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class DeclaredBy943(DeclaredBy): - pass - - -class VerifyAgent1874(VerifyAgent): - pass - - -class VerifyAgent1875(VerifyAgent1781): - pass - - -class VerificationItem937(VerificationItem): - pass - - -class DeclaredBy944(DeclaredBy): - pass - - -class VerifyAgent1876(VerifyAgent): - pass - - -class VerifyAgent1877(VerifyAgent1781): - pass - - -class VerificationItem938(VerificationItem): - pass - - -class AttributionWindow(NoticePeriod): - pass - - -class DeclaredBy945(DeclaredBy): - pass - - -class VerifyAgent1878(VerifyAgent): - pass - - -class VerifyAgent1879(VerifyAgent1781): - pass - - -class VerificationItem939(VerificationItem): - pass - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight for this creative (0–100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description="Optional array of structured placement references where this creative should run within the already-purchased package inventory. New senders SHOULD use this field for placement-level creative routing because placement IDs are publisher-scoped. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. References entries from the product's `placements[]` array by `{ publisher_domain, placement_id }`; if `publisher_domain` is omitted in the ref, receivers MAY interpret it relative to the seller agent's own publisher domain in legacy single-publisher contexts. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description="Legacy shorthand array of placement IDs where this creative should run within the already-purchased package inventory. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. Receivers MAY interpret string IDs relative to the seller agent's own publisher domain in legacy single-publisher contexts. If `placement_refs` is also present, receivers MUST ignore this field.", - min_length=1, - ), - ] = None - - -class FormatIdsToProvideItem(FormatId): - pass - - -class Window15(Window): - pass - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window15, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)] - - -class Target93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - float, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class PostClick(Window): - pass - - -class PostView(Window): - pass - - -class DeclaredBy946(DeclaredBy): - pass - - -class VerifyAgent1880(VerifyAgent): - pass - - -class VerifyAgent1881(VerifyAgent1781): - pass - - -class VerificationItem940(VerificationItem): - pass - - -class Target95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - countries: Annotated[ - list[str] | None, - Field(description='ISO 3166-1 alpha-2 country codes where ads will deliver.'), - ] = None - regions: Annotated[ - list[str] | None, Field(description='ISO 3166-2 subdivision codes where ads will deliver.') - ] = None - - -class Suppress4(Window): - pass - - -class Window16(Window): - pass - - - - - - -class AudienceTargeting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class AudienceTargeting7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class AudienceTargeting8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description='Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided.' - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description='Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided.' - ), - ] = None - - -class AudienceTargeting9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['description'], Field(description='Discriminator for description-based selectors') - ] = 'description' - description: Annotated[ - str, - Field( - description="Natural language description of the audience (e.g., 'likely EV buyers', 'high net worth individuals', 'vulnerable communities')", - max_length=2000, - min_length=1, - ), - ] - category: Annotated[ - str | None, - Field( - description="Optional grouping hint for the governance agent (e.g., 'demographic', 'behavioral', 'contextual', 'financial')" - ), - ] = None - - -class Issue100(Issue): - pass - - -class AdcpError49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue100] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue101(Issue): - pass - - -class Error46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue101] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue102(Issue): - pass - - -class AdcpError50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue102] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue103(Issue): - pass - - -class Error47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue103] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason26(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' - - -class Issue104(Issue): - pass - - -class Error48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue104] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason26 | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[Error48] | None, - Field( - description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue105(Issue): - pass - - -class Error49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue105] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose status field carries a MediaBuyStatus value (pending_creatives, pending_start, active). See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error49] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue106(Issue): - pass - - -class AdcpError51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue106] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Contact16(Contact): - pass - - -class InvoiceRecipient3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact16] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FeedFieldMapping37(FeedFieldMapping): - pass - - - -class FormatOptionRefs9(RootModel[FormatOptionRefs | FormatOptionRefs8]): - root: Annotated[ - FormatOptionRefs | FormatOptionRefs8, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas71(GeoPostalAreas51): - pass - - -class GeoPostalAreas72(GeoPostalAreas52): - pass - - -class GeoPostalAreas73(GeoPostalAreas53): - pass - - -class GeoPostalAreas74(GeoPostalAreas54): - pass - - -class GeoPostalAreas75(GeoPostalAreas55): - pass - - -class GeoPostalAreas76(GeoPostalAreas56): - pass - - -class GeoPostalAreas77(GeoPostalAreas57): - pass - - -class GeoPostalAreas78(GeoPostalAreas58): - pass - - -class GeoPostalAreas79(GeoPostalAreas59): - pass - - -class GeoPostalAreas710(GeoPostalAreas510): - pass - - -class GeoPostalAreasExclude271(GeoPostalAreas51): - pass - - -class GeoPostalAreasExclude272(GeoPostalAreas52): - pass - - -class GeoPostalAreasExclude273(GeoPostalAreas53): - pass - - -class GeoPostalAreasExclude274(GeoPostalAreas54): - pass - - -class GeoPostalAreasExclude275(GeoPostalAreas55): - pass - - -class GeoPostalAreasExclude276(GeoPostalAreas56): - pass - - -class GeoPostalAreasExclude277(GeoPostalAreas57): - pass - - -class GeoPostalAreasExclude278(GeoPostalAreas58): - pass - - -class GeoPostalAreasExclude279(GeoPostalAreas59): - pass - - -class GeoPostalAreasExclude2710(GeoPostalAreas510): - pass - - - - -class Signal71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - - -class Signal74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal75(Signal71, Signal74): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal76(Signal72, Signal74): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal77(Signal73, Signal74): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey28 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal7(RootModel[Signal75 | Signal76 | Signal77]): - root: Annotated[ - Signal75 | Signal76 | Signal77, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal7], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group3], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - - - -class SignalTargeting17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting16(RootModel[SignalTargeting17 | SignalTargeting18 | SignalTargeting19]): - root: Annotated[ - SignalTargeting17 | SignalTargeting18 | SignalTargeting19, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress5(Window): - pass - - -class Window17(Window): - pass - - -class Geometry6(Geometry): - pass - - -class DeclaredBy947(DeclaredBy): - pass - - -class VerifyAgent1882(VerifyAgent): - pass - - -class VerifyAgent1883(VerifyAgent1781): - pass - - -class VerificationItem941(VerificationItem): - pass - - -class DeclaredBy948(DeclaredBy): - pass - - -class VerifyAgent1884(VerifyAgent): - pass - - -class VerifyAgent1885(VerifyAgent1781): - pass - - -class VerificationItem942(VerificationItem): - pass - - -class AttributionWindow11(NoticePeriod): - pass - - -class DeclaredBy949(DeclaredBy): - pass - - -class VerifyAgent1886(VerifyAgent): - pass - - -class VerifyAgent1887(VerifyAgent1781): - pass - - -class VerificationItem943(VerificationItem): - pass - - -class CreativeAssignment4(CreativeAssignment): - pass - - -class Window18(Window): - pass - - -class TargetFrequency4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window18, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - - - - - -class PostClick4(Window): - pass - - -class PostView4(Window): - pass - - -class DeclaredBy950(DeclaredBy): - pass - - -class VerifyAgent1888(VerifyAgent): - pass - - -class VerifyAgent1889(VerifyAgent1781): - pass - - -class VerificationItem944(VerificationItem): - pass - - - -class Issue107(Issue): - pass - - -class AdcpError52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue107] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue108(Issue): - pass - - -class Error50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue108] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue109(Issue): - pass - - -class AdcpError53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue109] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue110(Issue): - pass - - -class Error51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue110] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1002(Result992): - pass - - -class Reason27(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' - - -class Result1003(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason27 | None, Field(description='Reason code indicating why input is needed') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue111(Issue): - pass - - -class Error52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue111] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1004(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error52] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class NotificationType3(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: AwareDatetime - end: AwareDatetime - - -class PostClick5(Window): - pass - - -class PostView5(Window): - pass - - -class Status314(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - pending = 'pending' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - failed = 'failed' - reporting_delayed = 'reporting_delayed' - - -class Kind23(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class Period24(Window): - pass - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind23, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period24 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class QuartileData(AdCPBaseModel): - q1_views: Annotated[float | None, Field(description='25% completion views', ge=0.0)] = None - q2_views: Annotated[float | None, Field(description='50% completion views', ge=0.0)] = None - q3_views: Annotated[float | None, Field(description='75% completion views', ge=0.0)] = None - q4_views: Annotated[float | None, Field(description='100% completion views', ge=0.0)] = None - - -class VenueBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - venue_id: Annotated[str, Field(description='Venue identifier')] - venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None - venue_type: Annotated[ - str | None, - Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), - ] = None - impressions: Annotated[int, Field(description='Impressions delivered at this venue', ge=0)] - loop_plays: Annotated[int | None, Field(description='Loop plays at this venue', ge=0)] = None - screens_used: Annotated[ - int | None, Field(description='Number of screens used at this venue', ge=0) - ] = None - - -class DoohMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - loop_plays: Annotated[ - int | None, Field(description='Number of times ad played in rotation', ge=0) - ] = None - screens_used: Annotated[ - int | None, Field(description='Number of unique screens displaying the ad', ge=0) - ] = None - screen_time_seconds: Annotated[ - int | None, Field(description='Total display time in seconds', ge=0) - ] = None - sov_achieved: Annotated[ - float | None, - Field(description='Actual share of voice delivered (0.0 to 1.0)', ge=0.0, le=1.0), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." - ), - ] = None - venue_breakdown: Annotated[ - list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') - ] = None - - -class DeclaredBy951(DeclaredBy): - pass - - -class VerifyAgent1890(VerifyAgent): - pass - - -class VerifyAgent1891(VerifyAgent1781): - pass - - -class VerificationItem945(VerificationItem): - pass - - -class DeclaredBy952(DeclaredBy): - pass - - -class VerifyAgent1892(VerifyAgent): - pass - - -class VerifyAgent1893(VerifyAgent1781): - pass - - -class VerificationItem946(VerificationItem): - pass - - -class Period25(Window): - pass - - -class ReachWindow7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind23, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period25 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics6(DoohMetrics): - pass - - -class DeclaredBy953(DeclaredBy): - pass - - -class VerifyAgent1894(VerifyAgent): - pass - - -class VerifyAgent1895(VerifyAgent1781): - pass - - -class VerificationItem947(VerificationItem): - pass - - -class DeclaredBy954(DeclaredBy): - pass - - -class VerifyAgent1896(VerifyAgent): - pass - - -class VerifyAgent1897(VerifyAgent1781): - pass - - -class VerificationItem948(VerificationItem): - pass - - -class DeliveryStatus(StrEnum): - delivering = 'delivering' - completed = 'completed' - budget_exhausted = 'budget_exhausted' - flight_ended = 'flight_ended' - goal_met = 'goal_met' - - -class Issue112(Issue): - pass - - -class Error53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue112] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue113(Issue): - pass - - -class AdcpError54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue113] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - -class DeclaredBy955(DeclaredBy): - pass - - -class VerifyAgent1898(VerifyAgent): - pass - - -class VerifyAgent1899(VerifyAgent1781): - pass - - -class VerificationItem949(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy956(DeclaredBy): - pass - - -class VerifyAgent1900(VerifyAgent): - pass - - -class VerifyAgent1901(VerifyAgent1781): - pass - - -class VerificationItem950(VerificationItem): - pass - - -class DeclaredBy957(DeclaredBy): - pass - - -class VerifyAgent1902(VerifyAgent): - pass - - -class VerifyAgent1903(VerifyAgent1781): - pass - - -class VerificationItem951(VerificationItem): - pass - - -class DeclaredBy958(DeclaredBy): - pass - - -class VerifyAgent1904(VerifyAgent): - pass - - -class VerifyAgent1905(VerifyAgent1781): - pass - - -class VerificationItem952(VerificationItem): - pass - - -class DeclaredBy959(DeclaredBy): - pass - - -class VerifyAgent1906(VerifyAgent): - pass - - -class VerifyAgent1907(VerifyAgent1781): - pass - - -class VerificationItem953(VerificationItem): - pass - - -class DeclaredBy960(DeclaredBy): - pass - - -class VerifyAgent1908(VerifyAgent): - pass - - -class VerifyAgent1909(VerifyAgent1781): - pass - - -class VerificationItem954(VerificationItem): - pass - - -class DeclaredBy961(DeclaredBy): - pass - - -class VerifyAgent1910(VerifyAgent): - pass - - -class VerifyAgent1911(VerifyAgent1781): - pass - - -class VerificationItem955(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy962(DeclaredBy): - pass - - -class VerifyAgent1912(VerifyAgent): - pass - - -class VerifyAgent1913(VerifyAgent1781): - pass - - -class VerificationItem956(VerificationItem): - pass - - -class DeclaredBy963(DeclaredBy): - pass - - -class VerifyAgent1914(VerifyAgent): - pass - - -class VerifyAgent1915(VerifyAgent1781): - pass - - -class VerificationItem957(VerificationItem): - pass - - -class DeclaredBy964(DeclaredBy): - pass - - -class VerifyAgent1916(VerifyAgent): - pass - - -class VerifyAgent1917(VerifyAgent1781): - pass - - -class VerificationItem958(VerificationItem): - pass - - -class DeclaredBy965(DeclaredBy): - pass - - -class VerifyAgent1918(VerifyAgent): - pass - - -class VerifyAgent1919(VerifyAgent1781): - pass - - -class VerificationItem959(VerificationItem): - pass - - -class DeclaredBy966(DeclaredBy): - pass - - -class VerifyAgent1920(VerifyAgent): - pass - - -class VerifyAgent1921(VerifyAgent1781): - pass - - -class VerificationItem960(VerificationItem): - pass - - -class DeclaredBy967(DeclaredBy): - pass - - -class VerifyAgent1922(VerifyAgent): - pass - - -class VerifyAgent1923(VerifyAgent1781): - pass - - -class VerificationItem961(VerificationItem): - pass - - -class DeclaredBy968(DeclaredBy): - pass - - -class VerifyAgent1924(VerifyAgent): - pass - - -class VerifyAgent1925(VerifyAgent1781): - pass - - -class VerificationItem962(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role1020(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role1020, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction1008(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class FeedFieldMapping38(FeedFieldMapping): - pass - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status315(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status315, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy969(DeclaredBy): - pass - - -class VerifyAgent1926(VerifyAgent): - pass - - -class VerifyAgent1927(VerifyAgent1781): - pass - - -class VerificationItem963(VerificationItem): - pass - - -class DeclaredBy970(DeclaredBy): - pass - - -class VerifyAgent1928(VerifyAgent): - pass - - -class VerifyAgent1929(VerifyAgent1781): - pass - - -class VerificationItem964(VerificationItem): - pass - - -class DeclaredBy971(DeclaredBy): - pass - - -class VerifyAgent1930(VerifyAgent): - pass - - -class VerifyAgent1931(VerifyAgent1781): - pass - - -class VerificationItem965(VerificationItem): - pass - - -class DeclaredBy972(DeclaredBy): - pass - - -class VerifyAgent1932(VerifyAgent): - pass - - -class VerifyAgent1933(VerifyAgent1781): - pass - - -class VerificationItem966(VerificationItem): - pass - - -class DeclaredBy973(DeclaredBy): - pass - - -class VerifyAgent1934(VerifyAgent): - pass - - -class VerifyAgent1935(VerifyAgent1781): - pass - - -class VerificationItem967(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method62(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy974(DeclaredBy): - pass - - -class VerifyAgent1936(VerifyAgent): - pass - - -class VerifyAgent1937(VerifyAgent1781): - pass - - -class VerificationItem968(VerificationItem): - pass - - -class Target104(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy975(DeclaredBy): - pass - - -class VerifyAgent1938(VerifyAgent): - pass - - -class VerifyAgent1939(VerifyAgent1781): - pass - - -class VerificationItem969(VerificationItem): - pass - - -class Target105(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy976(DeclaredBy): - pass - - -class VerifyAgent1940(VerifyAgent): - pass - - -class VerifyAgent1941(VerifyAgent1781): - pass - - -class VerificationItem970(VerificationItem): - pass - - -class DeclaredBy977(DeclaredBy): - pass - - -class VerifyAgent1942(VerifyAgent): - pass - - -class VerifyAgent1943(VerifyAgent1781): - pass - - -class VerificationItem971(VerificationItem): - pass - - -class DeclaredBy978(DeclaredBy): - pass - - -class VerifyAgent1944(VerifyAgent): - pass - - -class VerifyAgent1945(VerifyAgent1781): - pass - - -class VerificationItem972(VerificationItem): - pass - - -class DeclaredBy979(DeclaredBy): - pass - - -class VerifyAgent1946(VerifyAgent): - pass - - -class VerifyAgent1947(VerifyAgent1781): - pass - - -class VerificationItem973(VerificationItem): - pass - - -class DeclaredBy980(DeclaredBy): - pass - - -class VerifyAgent1948(VerifyAgent): - pass - - -class VerifyAgent1949(VerifyAgent1781): - pass - - -class VerificationItem974(VerificationItem): - pass - - -class DeclaredBy981(DeclaredBy): - pass - - -class VerifyAgent1950(VerifyAgent): - pass - - -class VerifyAgent1951(VerifyAgent1781): - pass - - -class VerificationItem975(VerificationItem): - pass - - -class DeclaredBy982(DeclaredBy): - pass - - -class VerifyAgent1952(VerifyAgent): - pass - - -class VerifyAgent1953(VerifyAgent1781): - pass - - -class VerificationItem976(VerificationItem): - pass - - -class DeclaredBy983(DeclaredBy): - pass - - -class VerifyAgent1954(VerifyAgent): - pass - - -class VerifyAgent1955(VerifyAgent1781): - pass - - -class VerificationItem977(VerificationItem): - pass - - -class DeclaredBy984(DeclaredBy): - pass - - -class VerifyAgent1956(VerifyAgent): - pass - - -class VerifyAgent1957(VerifyAgent1781): - pass - - -class VerificationItem978(VerificationItem): - pass - - -class DeclaredBy985(DeclaredBy): - pass - - -class VerifyAgent1958(VerifyAgent): - pass - - -class VerifyAgent1959(VerifyAgent1781): - pass - - -class VerificationItem979(VerificationItem): - pass - - -class DeclaredBy986(DeclaredBy): - pass - - -class VerifyAgent1960(VerifyAgent): - pass - - -class VerifyAgent1961(VerifyAgent1781): - pass - - -class VerificationItem980(VerificationItem): - pass - - -class DeclaredBy987(DeclaredBy): - pass - - -class VerifyAgent1962(VerifyAgent): - pass - - -class VerifyAgent1963(VerifyAgent1781): - pass - - -class VerificationItem981(VerificationItem): - pass - - -class DeclaredBy988(DeclaredBy): - pass - - -class VerifyAgent1964(VerifyAgent): - pass - - -class VerifyAgent1965(VerifyAgent1781): - pass - - -class VerificationItem982(VerificationItem): - pass - - -class DeclaredBy989(DeclaredBy): - pass - - -class VerifyAgent1966(VerifyAgent): - pass - - -class VerifyAgent1967(VerifyAgent1781): - pass - - -class VerificationItem983(VerificationItem): - pass - - -class DeclaredBy990(DeclaredBy): - pass - - -class VerifyAgent1968(VerifyAgent): - pass - - -class VerifyAgent1969(VerifyAgent1781): - pass - - -class VerificationItem984(VerificationItem): - pass - - -class ReferenceAsset36(ReferenceAsset): - pass - - -class FeedFieldMapping39(FeedFieldMapping): - pass - - -class ReferenceAuthorization36(ReferenceAuthorization): - pass - - -class DeclaredBy991(DeclaredBy): - pass - - -class VerifyAgent1970(VerifyAgent): - pass - - -class VerifyAgent1971(VerifyAgent1781): - pass - - -class VerificationItem985(VerificationItem): - pass - - -class DeclaredBy992(DeclaredBy): - pass - - -class VerifyAgent1972(VerifyAgent): - pass - - -class VerifyAgent1973(VerifyAgent1781): - pass - - -class VerificationItem986(VerificationItem): - pass - - -class DeclaredBy993(DeclaredBy): - pass - - -class VerifyAgent1974(VerifyAgent): - pass - - -class VerifyAgent1975(VerifyAgent1781): - pass - - -class VerificationItem987(VerificationItem): - pass - - -class DeclaredBy994(DeclaredBy): - pass - - -class VerifyAgent1976(VerifyAgent): - pass - - -class VerifyAgent1977(VerifyAgent1781): - pass - - -class VerificationItem988(VerificationItem): - pass - - -class DeclaredBy995(DeclaredBy): - pass - - -class VerifyAgent1978(VerifyAgent): - pass - - -class VerifyAgent1979(VerifyAgent1781): - pass - - -class VerificationItem989(VerificationItem): - pass - - -class DeclaredBy996(DeclaredBy): - pass - - -class VerifyAgent1980(VerifyAgent): - pass - - -class VerifyAgent1981(VerifyAgent1781): - pass - - -class VerificationItem990(VerificationItem): - pass - - -class DeclaredBy997(DeclaredBy): - pass - - -class VerifyAgent1982(VerifyAgent): - pass - - -class VerifyAgent1983(VerifyAgent1781): - pass - - -class VerificationItem991(VerificationItem): - pass - - -class DeclaredBy998(DeclaredBy): - pass - - -class VerifyAgent1984(VerifyAgent): - pass - - -class VerifyAgent1985(VerifyAgent1781): - pass - - -class VerificationItem992(VerificationItem): - pass - - -class DeclaredBy999(DeclaredBy): - pass - - -class VerifyAgent1986(VerifyAgent): - pass - - -class VerifyAgent1987(VerifyAgent1781): - pass - - -class VerificationItem993(VerificationItem): - pass - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Country94(Country): - pass - - -class ExcludedCountry(Country): - pass - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class DeclaredBy1000(DeclaredBy): - pass - - -class VerifyAgent1988(VerifyAgent): - pass - - -class VerifyAgent1989(VerifyAgent1781): - pass - - -class VerificationItem994(VerificationItem): - pass - - - -class DeclaredBy1001(DeclaredBy): - pass - - -class VerifyAgent1990(VerifyAgent): - pass - - -class VerifyAgent1991(VerifyAgent1781): - pass - - -class VerificationItem995(VerificationItem): - pass - - -class DeclaredBy1002(DeclaredBy): - pass - - -class VerifyAgent1992(VerifyAgent): - pass - - -class VerifyAgent1993(VerifyAgent1781): - pass - - -class VerificationItem996(VerificationItem): - pass - - -class DeclaredBy1003(DeclaredBy): - pass - - -class VerifyAgent1994(VerifyAgent): - pass - - -class VerifyAgent1995(VerifyAgent1781): - pass - - -class VerificationItem997(VerificationItem): - pass - - -class DeclaredBy1004(DeclaredBy): - pass - - -class VerifyAgent1996(VerifyAgent): - pass - - -class VerifyAgent1997(VerifyAgent1781): - pass - - -class VerificationItem998(VerificationItem): - pass - - -class DeclaredBy1005(DeclaredBy): - pass - - -class VerifyAgent1998(VerifyAgent): - pass - - -class VerifyAgent1999(VerifyAgent1781): - pass - - -class VerificationItem999(VerificationItem): - pass - - -class DeclaredBy1006(DeclaredBy): - pass - - -class VerifyAgent2000(VerifyAgent): - pass - - -class VerifyAgent2001(VerifyAgent1781): - pass - - -class VerificationItem1000(VerificationItem): - pass - - -class DeclaredBy1007(DeclaredBy): - pass - - -class VerifyAgent2002(VerifyAgent): - pass - - -class VerifyAgent2003(VerifyAgent1781): - pass - - -class VerificationItem1001(VerificationItem): - pass - - -class DeclaredBy1008(DeclaredBy): - pass - - -class VerifyAgent2004(VerifyAgent): - pass - - -class VerifyAgent2005(VerifyAgent1781): - pass - - -class VerificationItem1002(VerificationItem): - pass - - -class DeclaredBy1009(DeclaredBy): - pass - - -class VerifyAgent2006(VerifyAgent): - pass - - -class VerifyAgent2007(VerifyAgent1781): - pass - - -class VerificationItem1003(VerificationItem): - pass - - -class DeclaredBy1010(DeclaredBy): - pass - - -class VerifyAgent2008(VerifyAgent): - pass - - -class VerifyAgent2009(VerifyAgent1781): - pass - - -class VerificationItem1004(VerificationItem): - pass - - -class DeclaredBy1011(DeclaredBy): - pass - - -class VerifyAgent2010(VerifyAgent): - pass - - -class VerifyAgent2011(VerifyAgent1781): - pass - - -class VerificationItem1005(VerificationItem): - pass - - -class DeclaredBy1012(DeclaredBy): - pass - - -class VerifyAgent2012(VerifyAgent): - pass - - -class VerifyAgent2013(VerifyAgent1781): - pass - - -class VerificationItem1006(VerificationItem): - pass - - -class DeclaredBy1013(DeclaredBy): - pass - - -class VerifyAgent2014(VerifyAgent): - pass - - -class VerifyAgent2015(VerifyAgent1781): - pass - - -class VerificationItem1007(VerificationItem): - pass - - -class DeclaredBy1014(DeclaredBy): - pass - - -class VerifyAgent2016(VerifyAgent): - pass - - -class VerifyAgent2017(VerifyAgent1781): - pass - - -class VerificationItem1008(VerificationItem): - pass - - -class ReferenceAsset37(ReferenceAsset): - pass - - -class FeedFieldMapping40(FeedFieldMapping): - pass - - -class ReferenceAuthorization37(ReferenceAuthorization): - pass - - -class DeclaredBy1015(DeclaredBy): - pass - - -class VerifyAgent2018(VerifyAgent): - pass - - -class VerifyAgent2019(VerifyAgent1781): - pass - - -class VerificationItem1009(VerificationItem): - pass - - -class DeclaredBy1016(DeclaredBy): - pass - - -class VerifyAgent2020(VerifyAgent): - pass - - -class VerifyAgent2021(VerifyAgent1781): - pass - - -class VerificationItem1010(VerificationItem): - pass - - -class DeclaredBy1017(DeclaredBy): - pass - - -class VerifyAgent2022(VerifyAgent): - pass - - -class VerifyAgent2023(VerifyAgent1781): - pass - - -class VerificationItem1011(VerificationItem): - pass - - -class DeclaredBy1018(DeclaredBy): - pass - - -class VerifyAgent2024(VerifyAgent): - pass - - -class VerifyAgent2025(VerifyAgent1781): - pass - - -class VerificationItem1012(VerificationItem): - pass - - -class DeclaredBy1019(DeclaredBy): - pass - - -class VerifyAgent2026(VerifyAgent): - pass - - -class VerifyAgent2027(VerifyAgent1781): - pass - - -class VerificationItem1013(VerificationItem): - pass - - -class DeclaredBy1020(DeclaredBy): - pass - - -class VerifyAgent2028(VerifyAgent): - pass - - -class VerifyAgent2029(VerifyAgent1781): - pass - - -class VerificationItem1014(VerificationItem): - pass - - -class DeclaredBy1021(DeclaredBy): - pass - - -class VerifyAgent2030(VerifyAgent): - pass - - -class VerifyAgent2031(VerifyAgent1781): - pass - - -class VerificationItem1015(VerificationItem): - pass - - -class DeclaredBy1022(DeclaredBy): - pass - - -class VerifyAgent2032(VerifyAgent): - pass - - -class VerifyAgent2033(VerifyAgent1781): - pass - - -class VerificationItem1016(VerificationItem): - pass - - -class DeclaredBy1023(DeclaredBy): - pass - - -class VerifyAgent2034(VerifyAgent): - pass - - -class VerifyAgent2035(VerifyAgent1781): - pass - - -class VerificationItem1017(VerificationItem): - pass - - -class DeclaredBy1024(DeclaredBy): - pass - - -class VerifyAgent2036(VerifyAgent): - pass - - -class VerifyAgent2037(VerifyAgent1781): - pass - - -class VerificationItem1018(VerificationItem): - pass - - -class DeclaredBy1025(DeclaredBy): - pass - - -class VerifyAgent2038(VerifyAgent): - pass - - -class VerifyAgent2039(VerifyAgent1781): - pass - - -class VerificationItem1019(VerificationItem): - pass - - -class DeclaredBy1026(DeclaredBy): - pass - - -class VerifyAgent2040(VerifyAgent): - pass - - -class VerifyAgent2041(VerifyAgent1781): - pass - - -class VerificationItem1020(VerificationItem): - pass - - -class DeclaredBy1027(DeclaredBy): - pass - - -class VerifyAgent2042(VerifyAgent): - pass - - -class VerifyAgent2043(VerifyAgent1781): - pass - - -class VerificationItem1021(VerificationItem): - pass - - -class DeclaredBy1028(DeclaredBy): - pass - - -class VerifyAgent2044(VerifyAgent): - pass - - -class VerifyAgent2045(VerifyAgent1781): - pass - - -class VerificationItem1022(VerificationItem): - pass - - -class DeclaredBy1029(DeclaredBy): - pass - - -class VerifyAgent2046(VerifyAgent): - pass - - -class VerifyAgent2047(VerifyAgent1781): - pass - - -class VerificationItem1023(VerificationItem): - pass - - -class DeclaredBy1030(DeclaredBy): - pass - - -class VerifyAgent2048(VerifyAgent): - pass - - -class VerifyAgent2049(VerifyAgent1781): - pass - - -class VerificationItem1024(VerificationItem): - pass - - -class DeclaredBy1031(DeclaredBy): - pass - - -class VerifyAgent2050(VerifyAgent): - pass - - -class VerifyAgent2051(VerifyAgent1781): - pass - - -class VerificationItem1025(VerificationItem): - pass - - -class DeclaredBy1032(DeclaredBy): - pass - - -class VerifyAgent2052(VerifyAgent): - pass - - -class VerifyAgent2053(VerifyAgent1781): - pass - - -class VerificationItem1026(VerificationItem): - pass - - -class DeclaredBy1033(DeclaredBy): - pass - - -class VerifyAgent2054(VerifyAgent): - pass - - -class VerifyAgent2055(VerifyAgent1781): - pass - - -class VerificationItem1027(VerificationItem): - pass - - -class DeclaredBy1034(DeclaredBy): - pass - - -class VerifyAgent2056(VerifyAgent): - pass - - -class VerifyAgent2057(VerifyAgent1781): - pass - - -class VerificationItem1028(VerificationItem): - pass - - -class DeclaredBy1035(DeclaredBy): - pass - - -class VerifyAgent2058(VerifyAgent): - pass - - -class VerifyAgent2059(VerifyAgent1781): - pass - - -class VerificationItem1029(VerificationItem): - pass - - -class DeclaredBy1036(DeclaredBy): - pass - - -class VerifyAgent2060(VerifyAgent): - pass - - -class VerifyAgent2061(VerifyAgent1781): - pass - - -class VerificationItem1030(VerificationItem): - pass - - -class ReferenceAsset38(ReferenceAsset): - pass - - -class FeedFieldMapping41(FeedFieldMapping): - pass - - -class ReferenceAuthorization38(ReferenceAuthorization): - pass - - -class DeclaredBy1037(DeclaredBy): - pass - - -class VerifyAgent2062(VerifyAgent): - pass - - -class VerifyAgent2063(VerifyAgent1781): - pass - - -class VerificationItem1031(VerificationItem): - pass - - -class DeclaredBy1038(DeclaredBy): - pass - - -class VerifyAgent2064(VerifyAgent): - pass - - -class VerifyAgent2065(VerifyAgent1781): - pass - - -class VerificationItem1032(VerificationItem): - pass - - -class DeclaredBy1039(DeclaredBy): - pass - - -class VerifyAgent2066(VerifyAgent): - pass - - -class VerifyAgent2067(VerifyAgent1781): - pass - - -class VerificationItem1033(VerificationItem): - pass - - -class DeclaredBy1040(DeclaredBy): - pass - - -class VerifyAgent2068(VerifyAgent): - pass - - -class VerifyAgent2069(VerifyAgent1781): - pass - - -class VerificationItem1034(VerificationItem): - pass - - -class DeclaredBy1041(DeclaredBy): - pass - - -class VerifyAgent2070(VerifyAgent): - pass - - -class VerifyAgent2071(VerifyAgent1781): - pass - - -class VerificationItem1035(VerificationItem): - pass - - -class DeclaredBy1042(DeclaredBy): - pass - - -class VerifyAgent2072(VerifyAgent): - pass - - -class VerifyAgent2073(VerifyAgent1781): - pass - - -class VerificationItem1036(VerificationItem): - pass - - -class DeclaredBy1043(DeclaredBy): - pass - - -class VerifyAgent2074(VerifyAgent): - pass - - -class VerifyAgent2075(VerifyAgent1781): - pass - - -class VerificationItem1037(VerificationItem): - pass - - -class DeclaredBy1044(DeclaredBy): - pass - - -class VerifyAgent2076(VerifyAgent): - pass - - -class VerifyAgent2077(VerifyAgent1781): - pass - - -class VerificationItem1038(VerificationItem): - pass - - -class DeclaredBy1045(DeclaredBy): - pass - - -class VerifyAgent2078(VerifyAgent): - pass - - -class VerifyAgent2079(VerifyAgent1781): - pass - - -class VerificationItem1039(VerificationItem): - pass - - -class DeclaredBy1046(DeclaredBy): - pass - - -class VerifyAgent2080(VerifyAgent): - pass - - -class VerifyAgent2081(VerifyAgent1781): - pass - - -class VerificationItem1040(VerificationItem): - pass - - -class Dimensions103(AdCPBaseModel): - width: Annotated[float, Field(ge=0.0)] - height: Annotated[float, Field(ge=0.0)] - - -class Embedding(AdCPBaseModel): - recommended_sandbox: Annotated[ - str | None, - Field( - description="Recommended iframe sandbox attribute value (e.g., 'allow-scripts allow-same-origin')" - ), - ] = None - requires_https: Annotated[ - bool | None, Field(description='Whether this output requires HTTPS for secure embedding') - ] = None - supports_fullscreen: Annotated[ - bool | None, Field(description='Whether this output supports fullscreen mode') - ] = None - csp_policy: Annotated[ - str | None, Field(description='Content Security Policy requirements for embedding') - ] = None - - -class Renders(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['url'], Field(description='Discriminator indicating preview_url is provided') - ] = 'url' - preview_url: Annotated[ - AnyUrl, - Field( - description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions103 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - - -class Renders14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['html'], Field(description='Discriminator indicating preview_html is provided') - ] = 'html' - preview_html: Annotated[ - str, - Field( - description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions103 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, Field(description='Optional security and embedding metadata') - ] = None - - -class Renders15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['both'], - Field( - description='Discriminator indicating both preview_url and preview_html are provided' - ), - ] = 'both' - preview_url: Annotated[ - AnyUrl, - Field( - description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' - ), - ] - preview_html: Annotated[ - str, - Field( - description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions103 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - - -class Renders12(RootModel[Renders | Renders14 | Renders15]): - root: Annotated[ - Renders | Renders14 | Renders15, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values applied to this variant') - ] = None - context_description: Annotated[ - str | None, Field(description='Context description applied to this variant') - ] = None - - -class Preview(AdCPBaseModel): - preview_id: Annotated[str, Field(description='Unique identifier for this preview variant')] - renders: Annotated[ - list[Renders12], - Field( - description='Array of rendered pieces for this preview variant. Most formats render as a single piece. Companion ad formats render as multiple pieces.', - min_length=1, - ), - ] - input: Annotated[ - Input, - Field( - description='The input parameters that generated this preview variant. Echoes back the request input or shows defaults used.' - ), - ] - - -class Preview4(AdCPBaseModel): - previews: Annotated[ - list[Preview], - Field( - description='Array of preview variants. Each preview corresponds to an input set from preview_inputs, or a single default preview if no inputs were provided.', - min_length=1, - ), - ] - interactive_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to an interactive testing page that shows all preview variants with controls to switch between them.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when preview URLs expire. May differ from the manifest's expires_at." - ), - ] - - -class Issue114(Issue): - pass - - -class PreviewError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue114] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Consumption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - tokens: Annotated[ - int | None, - Field(description='LLM or generation tokens consumed during creative generation.', ge=0), - ] = None - images_generated: Annotated[ - int | None, Field(description='Number of images produced during generation.', ge=0) - ] = None - renders: Annotated[ - int | None, Field(description='Number of render passes performed (video, animation).', ge=0) - ] = None - duration_seconds: Annotated[ - float | None, - Field( - description='Processing time billed, in seconds. For compute-time pricing models.', - ge=0.0, - ), - ] = None - - -class Issue115(Issue): - pass - - -class AdcpError55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue115] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - -class DeclaredBy1047(DeclaredBy): - pass - - -class VerifyAgent2082(VerifyAgent): - pass - - -class VerifyAgent2083(VerifyAgent1781): - pass - - -class VerificationItem1041(VerificationItem): - pass - - -class DeclaredBy1048(DeclaredBy): - pass - - -class VerifyAgent2084(VerifyAgent): - pass - - -class VerifyAgent2085(VerifyAgent1781): - pass - - -class VerificationItem1042(VerificationItem): - pass - - -class DeclaredBy1049(DeclaredBy): - pass - - -class VerifyAgent2086(VerifyAgent): - pass - - -class VerifyAgent2087(VerifyAgent1781): - pass - - -class VerificationItem1043(VerificationItem): - pass - - -class DeclaredBy1050(DeclaredBy): - pass - - -class VerifyAgent2088(VerifyAgent): - pass - - -class VerifyAgent2089(VerifyAgent1781): - pass - - -class VerificationItem1044(VerificationItem): - pass - - -class DeclaredBy1051(DeclaredBy): - pass - - -class VerifyAgent2090(VerifyAgent): - pass - - -class VerifyAgent2091(VerifyAgent1781): - pass - - -class VerificationItem1045(VerificationItem): - pass - - -class DeclaredBy1052(DeclaredBy): - pass - - -class VerifyAgent2092(VerifyAgent): - pass - - -class VerifyAgent2093(VerifyAgent1781): - pass - - -class VerificationItem1046(VerificationItem): - pass - - -class DeclaredBy1053(DeclaredBy): - pass - - -class VerifyAgent2094(VerifyAgent): - pass - - -class VerifyAgent2095(VerifyAgent1781): - pass - - -class VerificationItem1047(VerificationItem): - pass - - -class DeclaredBy1054(DeclaredBy): - pass - - -class VerifyAgent2096(VerifyAgent): - pass - - -class VerifyAgent2097(VerifyAgent1781): - pass - - -class VerificationItem1048(VerificationItem): - pass - - -class DeclaredBy1055(DeclaredBy): - pass - - -class VerifyAgent2098(VerifyAgent): - pass - - -class VerifyAgent2099(VerifyAgent1781): - pass - - -class VerificationItem1049(VerificationItem): - pass - - -class DeclaredBy1056(DeclaredBy): - pass - - -class VerifyAgent2100(VerifyAgent): - pass - - -class VerifyAgent2101(VerifyAgent1781): - pass - - -class VerificationItem1050(VerificationItem): - pass - - -class DeclaredBy1057(DeclaredBy): - pass - - -class VerifyAgent2102(VerifyAgent): - pass - - -class VerifyAgent2103(VerifyAgent1781): - pass - - -class VerificationItem1051(VerificationItem): - pass - - -class DeclaredBy1058(DeclaredBy): - pass - - -class VerifyAgent2104(VerifyAgent): - pass - - -class VerifyAgent2105(VerifyAgent1781): - pass - - -class VerificationItem1052(VerificationItem): - pass - - -class DeclaredBy1059(DeclaredBy): - pass - - -class VerifyAgent2106(VerifyAgent): - pass - - -class VerifyAgent2107(VerifyAgent1781): - pass - - -class VerificationItem1053(VerificationItem): - pass - - -class DeclaredBy1060(DeclaredBy): - pass - - -class VerifyAgent2108(VerifyAgent): - pass - - -class VerifyAgent2109(VerifyAgent1781): - pass - - -class VerificationItem1054(VerificationItem): - pass - - -class ReferenceAsset39(ReferenceAsset): - pass - - -class FeedFieldMapping42(FeedFieldMapping): - pass - - -class ReferenceAuthorization39(ReferenceAuthorization): - pass - - -class DeclaredBy1061(DeclaredBy): - pass - - -class VerifyAgent2110(VerifyAgent): - pass - - -class VerifyAgent2111(VerifyAgent1781): - pass - - -class VerificationItem1055(VerificationItem): - pass - - -class DeclaredBy1062(DeclaredBy): - pass - - -class VerifyAgent2112(VerifyAgent): - pass - - -class VerifyAgent2113(VerifyAgent1781): - pass - - -class VerificationItem1056(VerificationItem): - pass - - -class DeclaredBy1063(DeclaredBy): - pass - - -class VerifyAgent2114(VerifyAgent): - pass - - -class VerifyAgent2115(VerifyAgent1781): - pass - - -class VerificationItem1057(VerificationItem): - pass - - -class DeclaredBy1064(DeclaredBy): - pass - - -class VerifyAgent2116(VerifyAgent): - pass - - -class VerifyAgent2117(VerifyAgent1781): - pass - - -class VerificationItem1058(VerificationItem): - pass - - -class DeclaredBy1065(DeclaredBy): - pass - - -class VerifyAgent2118(VerifyAgent): - pass - - -class VerifyAgent2119(VerifyAgent1781): - pass - - -class VerificationItem1059(VerificationItem): - pass - - -class DeclaredBy1066(DeclaredBy): - pass - - -class VerifyAgent2120(VerifyAgent): - pass - - -class VerifyAgent2121(VerifyAgent1781): - pass - - -class VerificationItem1060(VerificationItem): - pass - - -class DeclaredBy1067(DeclaredBy): - pass - - -class VerifyAgent2122(VerifyAgent): - pass - - -class VerifyAgent2123(VerifyAgent1781): - pass - - -class VerificationItem1061(VerificationItem): - pass - - -class DeclaredBy1068(DeclaredBy): - pass - - -class VerifyAgent2124(VerifyAgent): - pass - - -class VerifyAgent2125(VerifyAgent1781): - pass - - -class VerificationItem1062(VerificationItem): - pass - - -class DeclaredBy1069(DeclaredBy): - pass - - -class VerifyAgent2126(VerifyAgent): - pass - - -class VerifyAgent2127(VerifyAgent1781): - pass - - -class VerificationItem1063(VerificationItem): - pass - - -class DeclaredBy1070(DeclaredBy): - pass - - -class VerifyAgent2128(VerifyAgent): - pass - - -class VerifyAgent2129(VerifyAgent1781): - pass - - -class VerificationItem1064(VerificationItem): - pass - - -class DeclaredBy1071(DeclaredBy): - pass - - -class VerifyAgent2130(VerifyAgent): - pass - - -class VerifyAgent2131(VerifyAgent1781): - pass - - -class VerificationItem1065(VerificationItem): - pass - - -class DeclaredBy1072(DeclaredBy): - pass - - -class VerifyAgent2132(VerifyAgent): - pass - - -class VerifyAgent2133(VerifyAgent1781): - pass - - -class VerificationItem1066(VerificationItem): - pass - - -class DeclaredBy1073(DeclaredBy): - pass - - -class VerifyAgent2134(VerifyAgent): - pass - - -class VerifyAgent2135(VerifyAgent1781): - pass - - -class VerificationItem1067(VerificationItem): - pass - - -class DeclaredBy1074(DeclaredBy): - pass - - -class VerifyAgent2136(VerifyAgent): - pass - - -class VerifyAgent2137(VerifyAgent1781): - pass - - -class VerificationItem1068(VerificationItem): - pass - - -class DeclaredBy1075(DeclaredBy): - pass - - -class VerifyAgent2138(VerifyAgent): - pass - - -class VerifyAgent2139(VerifyAgent1781): - pass - - -class VerificationItem1069(VerificationItem): - pass - - -class DeclaredBy1076(DeclaredBy): - pass - - -class VerifyAgent2140(VerifyAgent): - pass - - -class VerifyAgent2141(VerifyAgent1781): - pass - - -class VerificationItem1070(VerificationItem): - pass - - -class DeclaredBy1077(DeclaredBy): - pass - - -class VerifyAgent2142(VerifyAgent): - pass - - -class VerifyAgent2143(VerifyAgent1781): - pass - - -class VerificationItem1071(VerificationItem): - pass - - -class DeclaredBy1078(DeclaredBy): - pass - - -class VerifyAgent2144(VerifyAgent): - pass - - -class VerifyAgent2145(VerifyAgent1781): - pass - - -class VerificationItem1072(VerificationItem): - pass - - -class DeclaredBy1079(DeclaredBy): - pass - - -class VerifyAgent2146(VerifyAgent): - pass - - -class VerifyAgent2147(VerifyAgent1781): - pass - - -class VerificationItem1073(VerificationItem): - pass - - -class DeclaredBy1080(DeclaredBy): - pass - - -class VerifyAgent2148(VerifyAgent): - pass - - -class VerifyAgent2149(VerifyAgent1781): - pass - - -class VerificationItem1074(VerificationItem): - pass - - -class DeclaredBy1081(DeclaredBy): - pass - - -class VerifyAgent2150(VerifyAgent): - pass - - -class VerifyAgent2151(VerifyAgent1781): - pass - - -class VerificationItem1075(VerificationItem): - pass - - -class DeclaredBy1082(DeclaredBy): - pass - - -class VerifyAgent2152(VerifyAgent): - pass - - -class VerifyAgent2153(VerifyAgent1781): - pass - - -class VerificationItem1076(VerificationItem): - pass - - -class ReferenceAsset40(ReferenceAsset): - pass - - -class FeedFieldMapping43(FeedFieldMapping): - pass - - -class ReferenceAuthorization40(ReferenceAuthorization): - pass - - -class DeclaredBy1083(DeclaredBy): - pass - - -class VerifyAgent2154(VerifyAgent): - pass - - -class VerifyAgent2155(VerifyAgent1781): - pass - - -class VerificationItem1077(VerificationItem): - pass - - -class DeclaredBy1084(DeclaredBy): - pass - - -class VerifyAgent2156(VerifyAgent): - pass - - -class VerifyAgent2157(VerifyAgent1781): - pass - - -class VerificationItem1078(VerificationItem): - pass - - -class DeclaredBy1085(DeclaredBy): - pass - - -class VerifyAgent2158(VerifyAgent): - pass - - -class VerifyAgent2159(VerifyAgent1781): - pass - - -class VerificationItem1079(VerificationItem): - pass - - -class DeclaredBy1086(DeclaredBy): - pass - - -class VerifyAgent2160(VerifyAgent): - pass - - -class VerifyAgent2161(VerifyAgent1781): - pass - - -class VerificationItem1080(VerificationItem): - pass - - -class DeclaredBy1087(DeclaredBy): - pass - - -class VerifyAgent2162(VerifyAgent): - pass - - -class VerifyAgent2163(VerifyAgent1781): - pass - - -class VerificationItem1081(VerificationItem): - pass - - -class DeclaredBy1088(DeclaredBy): - pass - - -class VerifyAgent2164(VerifyAgent): - pass - - -class VerifyAgent2165(VerifyAgent1781): - pass - - -class VerificationItem1082(VerificationItem): - pass - - -class DeclaredBy1089(DeclaredBy): - pass - - -class VerifyAgent2166(VerifyAgent): - pass - - -class VerifyAgent2167(VerifyAgent1781): - pass - - -class VerificationItem1083(VerificationItem): - pass - - -class DeclaredBy1090(DeclaredBy): - pass - - -class VerifyAgent2168(VerifyAgent): - pass - - -class VerifyAgent2169(VerifyAgent1781): - pass - - -class VerificationItem1084(VerificationItem): - pass - - -class DeclaredBy1091(DeclaredBy): - pass - - -class VerifyAgent2170(VerifyAgent): - pass - - -class VerifyAgent2171(VerifyAgent1781): - pass - - -class VerificationItem1085(VerificationItem): - pass - - -class DeclaredBy1092(DeclaredBy): - pass - - -class VerifyAgent2172(VerifyAgent): - pass - - -class VerifyAgent2173(VerifyAgent1781): - pass - - -class VerificationItem1086(VerificationItem): - pass - - - -class DeclaredBy1093(DeclaredBy): - pass - - -class VerifyAgent2174(VerifyAgent): - pass - - -class VerifyAgent2175(VerifyAgent1781): - pass - - -class VerificationItem1087(VerificationItem): - pass - - -class DeclaredBy1094(DeclaredBy): - pass - - -class VerifyAgent2176(VerifyAgent): - pass - - -class VerifyAgent2177(VerifyAgent1781): - pass - - -class VerificationItem1088(VerificationItem): - pass - - -class DeclaredBy1095(DeclaredBy): - pass - - -class VerifyAgent2178(VerifyAgent): - pass - - -class VerifyAgent2179(VerifyAgent1781): - pass - - -class VerificationItem1089(VerificationItem): - pass - - -class DeclaredBy1096(DeclaredBy): - pass - - -class VerifyAgent2180(VerifyAgent): - pass - - -class VerifyAgent2181(VerifyAgent1781): - pass - - -class VerificationItem1090(VerificationItem): - pass - - -class DeclaredBy1097(DeclaredBy): - pass - - -class VerifyAgent2182(VerifyAgent): - pass - - -class VerifyAgent2183(VerifyAgent1781): - pass - - -class VerificationItem1091(VerificationItem): - pass - - -class DeclaredBy1098(DeclaredBy): - pass - - -class VerifyAgent2184(VerifyAgent): - pass - - -class VerifyAgent2185(VerifyAgent1781): - pass - - -class VerificationItem1092(VerificationItem): - pass - - -class DeclaredBy1099(DeclaredBy): - pass - - -class VerifyAgent2186(VerifyAgent): - pass - - -class VerifyAgent2187(VerifyAgent1781): - pass - - -class VerificationItem1093(VerificationItem): - pass - - -class DeclaredBy1100(DeclaredBy): - pass - - -class VerifyAgent2188(VerifyAgent): - pass - - -class VerifyAgent2189(VerifyAgent1781): - pass - - -class VerificationItem1094(VerificationItem): - pass - - -class DeclaredBy1101(DeclaredBy): - pass - - -class VerifyAgent2190(VerifyAgent): - pass - - -class VerifyAgent2191(VerifyAgent1781): - pass - - -class VerificationItem1095(VerificationItem): - pass - - -class DeclaredBy1102(DeclaredBy): - pass - - -class VerifyAgent2192(VerifyAgent): - pass - - -class VerifyAgent2193(VerifyAgent1781): - pass - - -class VerificationItem1096(VerificationItem): - pass - - -class DeclaredBy1103(DeclaredBy): - pass - - -class VerifyAgent2194(VerifyAgent): - pass - - -class VerifyAgent2195(VerifyAgent1781): - pass - - -class VerificationItem1097(VerificationItem): - pass - - -class DeclaredBy1104(DeclaredBy): - pass - - -class VerifyAgent2196(VerifyAgent): - pass - - -class VerifyAgent2197(VerifyAgent1781): - pass - - -class VerificationItem1098(VerificationItem): - pass - - -class DeclaredBy1105(DeclaredBy): - pass - - -class VerifyAgent2198(VerifyAgent): - pass - - -class VerifyAgent2199(VerifyAgent1781): - pass - - -class VerificationItem1099(VerificationItem): - pass - - -class DeclaredBy1106(DeclaredBy): - pass - - -class VerifyAgent2200(VerifyAgent): - pass - - -class VerifyAgent2201(VerifyAgent1781): - pass - - -class VerificationItem1100(VerificationItem): - pass - - -class ReferenceAsset41(ReferenceAsset): - pass - - -class FeedFieldMapping44(FeedFieldMapping): - pass - - -class ReferenceAuthorization41(ReferenceAuthorization): - pass - - -class DeclaredBy1107(DeclaredBy): - pass - - -class VerifyAgent2202(VerifyAgent): - pass - - -class VerifyAgent2203(VerifyAgent1781): - pass - - -class VerificationItem1101(VerificationItem): - pass - - -class DeclaredBy1108(DeclaredBy): - pass - - -class VerifyAgent2204(VerifyAgent): - pass - - -class VerifyAgent2205(VerifyAgent1781): - pass - - -class VerificationItem1102(VerificationItem): - pass - - -class DeclaredBy1109(DeclaredBy): - pass - - -class VerifyAgent2206(VerifyAgent): - pass - - -class VerifyAgent2207(VerifyAgent1781): - pass - - -class VerificationItem1103(VerificationItem): - pass - - -class DeclaredBy1110(DeclaredBy): - pass - - -class VerifyAgent2208(VerifyAgent): - pass - - -class VerifyAgent2209(VerifyAgent1781): - pass - - -class VerificationItem1104(VerificationItem): - pass - - -class DeclaredBy1111(DeclaredBy): - pass - - -class VerifyAgent2210(VerifyAgent): - pass - - -class VerifyAgent2211(VerifyAgent1781): - pass - - -class VerificationItem1105(VerificationItem): - pass - - -class DeclaredBy1112(DeclaredBy): - pass - - -class VerifyAgent2212(VerifyAgent): - pass - - -class VerifyAgent2213(VerifyAgent1781): - pass - - -class VerificationItem1106(VerificationItem): - pass - - -class DeclaredBy1113(DeclaredBy): - pass - - -class VerifyAgent2214(VerifyAgent): - pass - - -class VerifyAgent2215(VerifyAgent1781): - pass - - -class VerificationItem1107(VerificationItem): - pass - - -class DeclaredBy1114(DeclaredBy): - pass - - -class VerifyAgent2216(VerifyAgent): - pass - - -class VerifyAgent2217(VerifyAgent1781): - pass - - -class VerificationItem1108(VerificationItem): - pass - - -class DeclaredBy1115(DeclaredBy): - pass - - -class VerifyAgent2218(VerifyAgent): - pass - - -class VerifyAgent2219(VerifyAgent1781): - pass - - -class VerificationItem1109(VerificationItem): - pass - - -class DeclaredBy1116(DeclaredBy): - pass - - -class VerifyAgent2220(VerifyAgent): - pass - - -class VerifyAgent2221(VerifyAgent1781): - pass - - -class VerificationItem1110(VerificationItem): - pass - - -class DeclaredBy1117(DeclaredBy): - pass - - -class VerifyAgent2222(VerifyAgent): - pass - - -class VerifyAgent2223(VerifyAgent1781): - pass - - -class VerificationItem1111(VerificationItem): - pass - - -class DeclaredBy1118(DeclaredBy): - pass - - -class VerifyAgent2224(VerifyAgent): - pass - - -class VerifyAgent2225(VerifyAgent1781): - pass - - -class VerificationItem1112(VerificationItem): - pass - - -class DeclaredBy1119(DeclaredBy): - pass - - -class VerifyAgent2226(VerifyAgent): - pass - - -class VerifyAgent2227(VerifyAgent1781): - pass - - -class VerificationItem1113(VerificationItem): - pass - - -class DeclaredBy1120(DeclaredBy): - pass - - -class VerifyAgent2228(VerifyAgent): - pass - - -class VerifyAgent2229(VerifyAgent1781): - pass - - -class VerificationItem1114(VerificationItem): - pass - - -class DeclaredBy1121(DeclaredBy): - pass - - -class VerifyAgent2230(VerifyAgent): - pass - - -class VerifyAgent2231(VerifyAgent1781): - pass - - -class VerificationItem1115(VerificationItem): - pass - - -class DeclaredBy1122(DeclaredBy): - pass - - -class VerifyAgent2232(VerifyAgent): - pass - - -class VerifyAgent2233(VerifyAgent1781): - pass - - -class VerificationItem1116(VerificationItem): - pass - - -class DeclaredBy1123(DeclaredBy): - pass - - -class VerifyAgent2234(VerifyAgent): - pass - - -class VerifyAgent2235(VerifyAgent1781): - pass - - -class VerificationItem1117(VerificationItem): - pass - - -class DeclaredBy1124(DeclaredBy): - pass - - -class VerifyAgent2236(VerifyAgent): - pass - - -class VerifyAgent2237(VerifyAgent1781): - pass - - -class VerificationItem1118(VerificationItem): - pass - - -class DeclaredBy1125(DeclaredBy): - pass - - -class VerifyAgent2238(VerifyAgent): - pass - - -class VerifyAgent2239(VerifyAgent1781): - pass - - -class VerificationItem1119(VerificationItem): - pass - - -class DeclaredBy1126(DeclaredBy): - pass - - -class VerifyAgent2240(VerifyAgent): - pass - - -class VerifyAgent2241(VerifyAgent1781): - pass - - -class VerificationItem1120(VerificationItem): - pass - - -class DeclaredBy1127(DeclaredBy): - pass - - -class VerifyAgent2242(VerifyAgent): - pass - - -class VerifyAgent2243(VerifyAgent1781): - pass - - -class VerificationItem1121(VerificationItem): - pass - - -class DeclaredBy1128(DeclaredBy): - pass - - -class VerifyAgent2244(VerifyAgent): - pass - - -class VerifyAgent2245(VerifyAgent1781): - pass - - -class VerificationItem1122(VerificationItem): - pass - - -class ReferenceAsset42(ReferenceAsset): - pass - - -class FeedFieldMapping45(FeedFieldMapping): - pass - - -class ReferenceAuthorization42(ReferenceAuthorization): - pass - - -class DeclaredBy1129(DeclaredBy): - pass - - -class VerifyAgent2246(VerifyAgent): - pass - - -class VerifyAgent2247(VerifyAgent1781): - pass - - -class VerificationItem1123(VerificationItem): - pass - - -class DeclaredBy1130(DeclaredBy): - pass - - -class VerifyAgent2248(VerifyAgent): - pass - - -class VerifyAgent2249(VerifyAgent1781): - pass - - -class VerificationItem1124(VerificationItem): - pass - - -class DeclaredBy1131(DeclaredBy): - pass - - -class VerifyAgent2250(VerifyAgent): - pass - - -class VerifyAgent2251(VerifyAgent1781): - pass - - -class VerificationItem1125(VerificationItem): - pass - - -class DeclaredBy1132(DeclaredBy): - pass - - -class VerifyAgent2252(VerifyAgent): - pass - - -class VerifyAgent2253(VerifyAgent1781): - pass - - -class VerificationItem1126(VerificationItem): - pass - - -class DeclaredBy1133(DeclaredBy): - pass - - -class VerifyAgent2254(VerifyAgent): - pass - - -class VerifyAgent2255(VerifyAgent1781): - pass - - -class VerificationItem1127(VerificationItem): - pass - - -class DeclaredBy1134(DeclaredBy): - pass - - -class VerifyAgent2256(VerifyAgent): - pass - - -class VerifyAgent2257(VerifyAgent1781): - pass - - -class VerificationItem1128(VerificationItem): - pass - - -class DeclaredBy1135(DeclaredBy): - pass - - -class VerifyAgent2258(VerifyAgent): - pass - - -class VerifyAgent2259(VerifyAgent1781): - pass - - -class VerificationItem1129(VerificationItem): - pass - - -class DeclaredBy1136(DeclaredBy): - pass - - -class VerifyAgent2260(VerifyAgent): - pass - - -class VerifyAgent2261(VerifyAgent1781): - pass - - -class VerificationItem1130(VerificationItem): - pass - - -class DeclaredBy1137(DeclaredBy): - pass - - -class VerifyAgent2262(VerifyAgent): - pass - - -class VerifyAgent2263(VerifyAgent1781): - pass - - -class VerificationItem1131(VerificationItem): - pass - - -class DeclaredBy1138(DeclaredBy): - pass - - -class VerifyAgent2264(VerifyAgent): - pass - - -class VerifyAgent2265(VerifyAgent1781): - pass - - -class VerificationItem1132(VerificationItem): - pass - - - - -class Renders16(RootModel[Renders | Renders14 | Renders15]): - root: Annotated[ - Renders | Renders14 | Renders15, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Input11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values applied to this preview') - ] = None - context_description: Annotated[ - str | None, Field(description='Context description applied to this preview') - ] = None - - -class Preview7(AdCPBaseModel): - preview_id: Annotated[str, Field(description='Unique identifier for this preview')] - format_id: Annotated[ - FormatId, - Field( - description='The format this preview was generated for. Matches one of the requested target_format_ids.', - title='Format Reference (Structured Object)', - ), - ] - renders: Annotated[ - list[Renders16], - Field( - description="Array of rendered pieces for this format's preview. Most formats render as a single piece. Companion ad formats render as multiple pieces.", - min_length=1, - ), - ] - input: Annotated[ - Input11, - Field( - description='The input parameters that generated this preview. For multi-format responses, this is always a default input.' - ), - ] - - -class Preview6(AdCPBaseModel): - previews: Annotated[ - list[Preview7], - Field( - description='Array of preview entries, one per requested format. Array order matches creative_manifests. Each entry includes a format_id for explicit correlation.', - min_length=1, - ), - ] - interactive_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to an interactive testing page that shows all format previews with controls to switch between them.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when preview URLs expire. May differ from the manifest's expires_at." - ), - ] - - -class Issue116(Issue): - pass - - -class PreviewError3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue116] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue117(Issue): - pass - - -class AdcpError56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue117] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class CatalogItemRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_type: Annotated[ - str | None, Field(description='The catalog type the item came from.') - ] = None - item_id: Annotated[ - str, Field(description='Identifier of the catalog item this creative was built for.') - ] - - - - - - -class SignalCondition(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalCondition12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalCondition13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - - -class DeclaredBy1139(DeclaredBy): - pass - - -class VerifyAgent2266(VerifyAgent): - pass - - -class VerifyAgent2267(VerifyAgent1781): - pass - - -class VerificationItem1133(VerificationItem): - pass - - -class DeclaredBy1140(DeclaredBy): - pass - - -class VerifyAgent2268(VerifyAgent): - pass - - -class VerifyAgent2269(VerifyAgent1781): - pass - - -class VerificationItem1134(VerificationItem): - pass - - -class DeclaredBy1141(DeclaredBy): - pass - - -class VerifyAgent2270(VerifyAgent): - pass - - -class VerifyAgent2271(VerifyAgent1781): - pass - - -class VerificationItem1135(VerificationItem): - pass - - -class DeclaredBy1142(DeclaredBy): - pass - - -class VerifyAgent2272(VerifyAgent): - pass - - -class VerifyAgent2273(VerifyAgent1781): - pass - - -class VerificationItem1136(VerificationItem): - pass - - -class DeclaredBy1143(DeclaredBy): - pass - - -class VerifyAgent2274(VerifyAgent): - pass - - -class VerifyAgent2275(VerifyAgent1781): - pass - - -class VerificationItem1137(VerificationItem): - pass - - -class DeclaredBy1144(DeclaredBy): - pass - - -class VerifyAgent2276(VerifyAgent): - pass - - -class VerifyAgent2277(VerifyAgent1781): - pass - - -class VerificationItem1138(VerificationItem): - pass - - -class DeclaredBy1145(DeclaredBy): - pass - - -class VerifyAgent2278(VerifyAgent): - pass - - -class VerifyAgent2279(VerifyAgent1781): - pass - - -class VerificationItem1139(VerificationItem): - pass - - -class DeclaredBy1146(DeclaredBy): - pass - - -class VerifyAgent2280(VerifyAgent): - pass - - -class VerifyAgent2281(VerifyAgent1781): - pass - - -class VerificationItem1140(VerificationItem): - pass - - -class DeclaredBy1147(DeclaredBy): - pass - - -class VerifyAgent2282(VerifyAgent): - pass - - -class VerifyAgent2283(VerifyAgent1781): - pass - - -class VerificationItem1141(VerificationItem): - pass - - -class DeclaredBy1148(DeclaredBy): - pass - - -class VerifyAgent2284(VerifyAgent): - pass - - -class VerifyAgent2285(VerifyAgent1781): - pass - - -class VerificationItem1142(VerificationItem): - pass - - -class DeclaredBy1149(DeclaredBy): - pass - - -class VerifyAgent2286(VerifyAgent): - pass - - -class VerifyAgent2287(VerifyAgent1781): - pass - - -class VerificationItem1143(VerificationItem): - pass - - -class DeclaredBy1150(DeclaredBy): - pass - - -class VerifyAgent2288(VerifyAgent): - pass - - -class VerifyAgent2289(VerifyAgent1781): - pass - - -class VerificationItem1144(VerificationItem): - pass - - -class DeclaredBy1151(DeclaredBy): - pass - - -class VerifyAgent2290(VerifyAgent): - pass - - -class VerifyAgent2291(VerifyAgent1781): - pass - - -class VerificationItem1145(VerificationItem): - pass - - -class DeclaredBy1152(DeclaredBy): - pass - - -class VerifyAgent2292(VerifyAgent): - pass - - -class VerifyAgent2293(VerifyAgent1781): - pass - - -class VerificationItem1146(VerificationItem): - pass - - -class ReferenceAsset43(ReferenceAsset): - pass - - -class FeedFieldMapping46(FeedFieldMapping): - pass - - -class ReferenceAuthorization43(ReferenceAuthorization): - pass - - -class DeclaredBy1153(DeclaredBy): - pass - - -class VerifyAgent2294(VerifyAgent): - pass - - -class VerifyAgent2295(VerifyAgent1781): - pass - - -class VerificationItem1147(VerificationItem): - pass - - -class DeclaredBy1154(DeclaredBy): - pass - - -class VerifyAgent2296(VerifyAgent): - pass - - -class VerifyAgent2297(VerifyAgent1781): - pass - - -class VerificationItem1148(VerificationItem): - pass - - -class DeclaredBy1155(DeclaredBy): - pass - - -class VerifyAgent2298(VerifyAgent): - pass - - -class VerifyAgent2299(VerifyAgent1781): - pass - - -class VerificationItem1149(VerificationItem): - pass - - -class DeclaredBy1156(DeclaredBy): - pass - - -class VerifyAgent2300(VerifyAgent): - pass - - -class VerifyAgent2301(VerifyAgent1781): - pass - - -class VerificationItem1150(VerificationItem): - pass - - -class DeclaredBy1157(DeclaredBy): - pass - - -class VerifyAgent2302(VerifyAgent): - pass - - -class VerifyAgent2303(VerifyAgent1781): - pass - - -class VerificationItem1151(VerificationItem): - pass - - -class DeclaredBy1158(DeclaredBy): - pass - - -class VerifyAgent2304(VerifyAgent): - pass - - -class VerifyAgent2305(VerifyAgent1781): - pass - - -class VerificationItem1152(VerificationItem): - pass - - -class DeclaredBy1159(DeclaredBy): - pass - - -class VerifyAgent2306(VerifyAgent): - pass - - -class VerifyAgent2307(VerifyAgent1781): - pass - - -class VerificationItem1153(VerificationItem): - pass - - -class DeclaredBy1160(DeclaredBy): - pass - - -class VerifyAgent2308(VerifyAgent): - pass - - -class VerifyAgent2309(VerifyAgent1781): - pass - - -class VerificationItem1154(VerificationItem): - pass - - -class DeclaredBy1161(DeclaredBy): - pass - - -class VerifyAgent2310(VerifyAgent): - pass - - -class VerifyAgent2311(VerifyAgent1781): - pass - - -class VerificationItem1155(VerificationItem): - pass - - -class DeclaredBy1162(DeclaredBy): - pass - - -class VerifyAgent2312(VerifyAgent): - pass - - -class VerifyAgent2313(VerifyAgent1781): - pass - - -class VerificationItem1156(VerificationItem): - pass - - -class DeclaredBy1163(DeclaredBy): - pass - - -class VerifyAgent2314(VerifyAgent): - pass - - -class VerifyAgent2315(VerifyAgent1781): - pass - - -class VerificationItem1157(VerificationItem): - pass - - -class DeclaredBy1164(DeclaredBy): - pass - - -class VerifyAgent2316(VerifyAgent): - pass - - -class VerifyAgent2317(VerifyAgent1781): - pass - - -class VerificationItem1158(VerificationItem): - pass - - -class DeclaredBy1165(DeclaredBy): - pass - - -class VerifyAgent2318(VerifyAgent): - pass - - -class VerifyAgent2319(VerifyAgent1781): - pass - - -class VerificationItem1159(VerificationItem): - pass - - -class DeclaredBy1166(DeclaredBy): - pass - - -class VerifyAgent2320(VerifyAgent): - pass - - -class VerifyAgent2321(VerifyAgent1781): - pass - - -class VerificationItem1160(VerificationItem): - pass - - -class DeclaredBy1167(DeclaredBy): - pass - - -class VerifyAgent2322(VerifyAgent): - pass - - -class VerifyAgent2323(VerifyAgent1781): - pass - - -class VerificationItem1161(VerificationItem): - pass - - -class DeclaredBy1168(DeclaredBy): - pass - - -class VerifyAgent2324(VerifyAgent): - pass - - -class VerifyAgent2325(VerifyAgent1781): - pass - - -class VerificationItem1162(VerificationItem): - pass - - -class DeclaredBy1169(DeclaredBy): - pass - - -class VerifyAgent2326(VerifyAgent): - pass - - -class VerifyAgent2327(VerifyAgent1781): - pass - - -class VerificationItem1163(VerificationItem): - pass - - -class DeclaredBy1170(DeclaredBy): - pass - - -class VerifyAgent2328(VerifyAgent): - pass - - -class VerifyAgent2329(VerifyAgent1781): - pass - - -class VerificationItem1164(VerificationItem): - pass - - -class DeclaredBy1171(DeclaredBy): - pass - - -class VerifyAgent2330(VerifyAgent): - pass - - -class VerifyAgent2331(VerifyAgent1781): - pass - - -class VerificationItem1165(VerificationItem): - pass - - -class DeclaredBy1172(DeclaredBy): - pass - - -class VerifyAgent2332(VerifyAgent): - pass - - -class VerifyAgent2333(VerifyAgent1781): - pass - - -class VerificationItem1166(VerificationItem): - pass - - -class DeclaredBy1173(DeclaredBy): - pass - - -class VerifyAgent2334(VerifyAgent): - pass - - -class VerifyAgent2335(VerifyAgent1781): - pass - - -class VerificationItem1167(VerificationItem): - pass - - -class DeclaredBy1174(DeclaredBy): - pass - - -class VerifyAgent2336(VerifyAgent): - pass - - -class VerifyAgent2337(VerifyAgent1781): - pass - - -class VerificationItem1168(VerificationItem): - pass - - -class ReferenceAsset44(ReferenceAsset): - pass - - -class FeedFieldMapping47(FeedFieldMapping): - pass - - -class ReferenceAuthorization44(ReferenceAuthorization): - pass - - -class DeclaredBy1175(DeclaredBy): - pass - - -class VerifyAgent2338(VerifyAgent): - pass - - -class VerifyAgent2339(VerifyAgent1781): - pass - - -class VerificationItem1169(VerificationItem): - pass - - -class DeclaredBy1176(DeclaredBy): - pass - - -class VerifyAgent2340(VerifyAgent): - pass - - -class VerifyAgent2341(VerifyAgent1781): - pass - - -class VerificationItem1170(VerificationItem): - pass - - -class DeclaredBy1177(DeclaredBy): - pass - - -class VerifyAgent2342(VerifyAgent): - pass - - -class VerifyAgent2343(VerifyAgent1781): - pass - - -class VerificationItem1171(VerificationItem): - pass - - -class DeclaredBy1178(DeclaredBy): - pass - - -class VerifyAgent2344(VerifyAgent): - pass - - -class VerifyAgent2345(VerifyAgent1781): - pass - - -class VerificationItem1172(VerificationItem): - pass - - -class DeclaredBy1179(DeclaredBy): - pass - - -class VerifyAgent2346(VerifyAgent): - pass - - -class VerifyAgent2347(VerifyAgent1781): - pass - - -class VerificationItem1173(VerificationItem): - pass - - -class DeclaredBy1180(DeclaredBy): - pass - - -class VerifyAgent2348(VerifyAgent): - pass - - -class VerifyAgent2349(VerifyAgent1781): - pass - - -class VerificationItem1174(VerificationItem): - pass - - -class DeclaredBy1181(DeclaredBy): - pass - - -class VerifyAgent2350(VerifyAgent): - pass - - -class VerifyAgent2351(VerifyAgent1781): - pass - - -class VerificationItem1175(VerificationItem): - pass - - -class DeclaredBy1182(DeclaredBy): - pass - - -class VerifyAgent2352(VerifyAgent): - pass - - -class VerifyAgent2353(VerifyAgent1781): - pass - - -class VerificationItem1176(VerificationItem): - pass - - -class DeclaredBy1183(DeclaredBy): - pass - - -class VerifyAgent2354(VerifyAgent): - pass - - -class VerifyAgent2355(VerifyAgent1781): - pass - - -class VerificationItem1177(VerificationItem): - pass - - -class DeclaredBy1184(DeclaredBy): - pass - - -class VerifyAgent2356(VerifyAgent): - pass - - -class VerifyAgent2357(VerifyAgent1781): - pass - - -class VerificationItem1178(VerificationItem): - pass - - - -class DeclaredBy1185(DeclaredBy): - pass - - -class VerifyAgent2358(VerifyAgent): - pass - - -class VerifyAgent2359(VerifyAgent1781): - pass - - -class VerificationItem1179(VerificationItem): - pass - - -class DeclaredBy1186(DeclaredBy): - pass - - -class VerifyAgent2360(VerifyAgent): - pass - - -class VerifyAgent2361(VerifyAgent1781): - pass - - -class VerificationItem1180(VerificationItem): - pass - - -class DeclaredBy1187(DeclaredBy): - pass - - -class VerifyAgent2362(VerifyAgent): - pass - - -class VerifyAgent2363(VerifyAgent1781): - pass - - -class VerificationItem1181(VerificationItem): - pass - - -class DeclaredBy1188(DeclaredBy): - pass - - -class VerifyAgent2364(VerifyAgent): - pass - - -class VerifyAgent2365(VerifyAgent1781): - pass - - -class VerificationItem1182(VerificationItem): - pass - - -class DeclaredBy1189(DeclaredBy): - pass - - -class VerifyAgent2366(VerifyAgent): - pass - - -class VerifyAgent2367(VerifyAgent1781): - pass - - -class VerificationItem1183(VerificationItem): - pass - - -class DeclaredBy1190(DeclaredBy): - pass - - -class VerifyAgent2368(VerifyAgent): - pass - - -class VerifyAgent2369(VerifyAgent1781): - pass - - -class VerificationItem1184(VerificationItem): - pass - - -class DeclaredBy1191(DeclaredBy): - pass - - -class VerifyAgent2370(VerifyAgent): - pass - - -class VerifyAgent2371(VerifyAgent1781): - pass - - -class VerificationItem1185(VerificationItem): - pass - - -class DeclaredBy1192(DeclaredBy): - pass - - -class VerifyAgent2372(VerifyAgent): - pass - - -class VerifyAgent2373(VerifyAgent1781): - pass - - -class VerificationItem1186(VerificationItem): - pass - - -class DeclaredBy1193(DeclaredBy): - pass - - -class VerifyAgent2374(VerifyAgent): - pass - - -class VerifyAgent2375(VerifyAgent1781): - pass - - -class VerificationItem1187(VerificationItem): - pass - - -class DeclaredBy1194(DeclaredBy): - pass - - -class VerifyAgent2376(VerifyAgent): - pass - - -class VerifyAgent2377(VerifyAgent1781): - pass - - -class VerificationItem1188(VerificationItem): - pass - - -class DeclaredBy1195(DeclaredBy): - pass - - -class VerifyAgent2378(VerifyAgent): - pass - - -class VerifyAgent2379(VerifyAgent1781): - pass - - -class VerificationItem1189(VerificationItem): - pass - - -class DeclaredBy1196(DeclaredBy): - pass - - -class VerifyAgent2380(VerifyAgent): - pass - - -class VerifyAgent2381(VerifyAgent1781): - pass - - -class VerificationItem1190(VerificationItem): - pass - - -class DeclaredBy1197(DeclaredBy): - pass - - -class VerifyAgent2382(VerifyAgent): - pass - - -class VerifyAgent2383(VerifyAgent1781): - pass - - -class VerificationItem1191(VerificationItem): - pass - - -class DeclaredBy1198(DeclaredBy): - pass - - -class VerifyAgent2384(VerifyAgent): - pass - - -class VerifyAgent2385(VerifyAgent1781): - pass - - -class VerificationItem1192(VerificationItem): - pass - - -class ReferenceAsset45(ReferenceAsset): - pass - - -class FeedFieldMapping48(FeedFieldMapping): - pass - - -class ReferenceAuthorization45(ReferenceAuthorization): - pass - - -class DeclaredBy1199(DeclaredBy): - pass - - -class VerifyAgent2386(VerifyAgent): - pass - - -class VerifyAgent2387(VerifyAgent1781): - pass - - -class VerificationItem1193(VerificationItem): - pass - - -class DeclaredBy1200(DeclaredBy): - pass - - -class VerifyAgent2388(VerifyAgent): - pass - - -class VerifyAgent2389(VerifyAgent1781): - pass - - -class VerificationItem1194(VerificationItem): - pass - - -class DeclaredBy1201(DeclaredBy): - pass - - -class VerifyAgent2390(VerifyAgent): - pass - - -class VerifyAgent2391(VerifyAgent1781): - pass - - -class VerificationItem1195(VerificationItem): - pass - - -class DeclaredBy1202(DeclaredBy): - pass - - -class VerifyAgent2392(VerifyAgent): - pass - - -class VerifyAgent2393(VerifyAgent1781): - pass - - -class VerificationItem1196(VerificationItem): - pass - - -class DeclaredBy1203(DeclaredBy): - pass - - -class VerifyAgent2394(VerifyAgent): - pass - - -class VerifyAgent2395(VerifyAgent1781): - pass - - -class VerificationItem1197(VerificationItem): - pass - - -class DeclaredBy1204(DeclaredBy): - pass - - -class VerifyAgent2396(VerifyAgent): - pass - - -class VerifyAgent2397(VerifyAgent1781): - pass - - -class VerificationItem1198(VerificationItem): - pass - - -class DeclaredBy1205(DeclaredBy): - pass - - -class VerifyAgent2398(VerifyAgent): - pass - - -class VerifyAgent2399(VerifyAgent1781): - pass - - -class VerificationItem1199(VerificationItem): - pass - - -class DeclaredBy1206(DeclaredBy): - pass - - -class VerifyAgent2400(VerifyAgent): - pass - - -class VerifyAgent2401(VerifyAgent1781): - pass - - -class VerificationItem1200(VerificationItem): - pass - - -class DeclaredBy1207(DeclaredBy): - pass - - -class VerifyAgent2402(VerifyAgent): - pass - - -class VerifyAgent2403(VerifyAgent1781): - pass - - -class VerificationItem1201(VerificationItem): - pass - - -class DeclaredBy1208(DeclaredBy): - pass - - -class VerifyAgent2404(VerifyAgent): - pass - - -class VerifyAgent2405(VerifyAgent1781): - pass - - -class VerificationItem1202(VerificationItem): - pass - - -class DeclaredBy1209(DeclaredBy): - pass - - -class VerifyAgent2406(VerifyAgent): - pass - - -class VerifyAgent2407(VerifyAgent1781): - pass - - -class VerificationItem1203(VerificationItem): - pass - - -class DeclaredBy1210(DeclaredBy): - pass - - -class VerifyAgent2408(VerifyAgent): - pass - - -class VerifyAgent2409(VerifyAgent1781): - pass - - -class VerificationItem1204(VerificationItem): - pass - - -class DeclaredBy1211(DeclaredBy): - pass - - -class VerifyAgent2410(VerifyAgent): - pass - - -class VerifyAgent2411(VerifyAgent1781): - pass - - -class VerificationItem1205(VerificationItem): - pass - - -class DeclaredBy1212(DeclaredBy): - pass - - -class VerifyAgent2412(VerifyAgent): - pass - - -class VerifyAgent2413(VerifyAgent1781): - pass - - -class VerificationItem1206(VerificationItem): - pass - - -class DeclaredBy1213(DeclaredBy): - pass - - -class VerifyAgent2414(VerifyAgent): - pass - - -class VerifyAgent2415(VerifyAgent1781): - pass - - -class VerificationItem1207(VerificationItem): - pass - - -class DeclaredBy1214(DeclaredBy): - pass - - -class VerifyAgent2416(VerifyAgent): - pass - - -class VerifyAgent2417(VerifyAgent1781): - pass - - -class VerificationItem1208(VerificationItem): - pass - - -class DeclaredBy1215(DeclaredBy): - pass - - -class VerifyAgent2418(VerifyAgent): - pass - - -class VerifyAgent2419(VerifyAgent1781): - pass - - -class VerificationItem1209(VerificationItem): - pass - - -class DeclaredBy1216(DeclaredBy): - pass - - -class VerifyAgent2420(VerifyAgent): - pass - - -class VerifyAgent2421(VerifyAgent1781): - pass - - -class VerificationItem1210(VerificationItem): - pass - - -class DeclaredBy1217(DeclaredBy): - pass - - -class VerifyAgent2422(VerifyAgent): - pass - - -class VerifyAgent2423(VerifyAgent1781): - pass - - -class VerificationItem1211(VerificationItem): - pass - - -class DeclaredBy1218(DeclaredBy): - pass - - -class VerifyAgent2424(VerifyAgent): - pass - - -class VerifyAgent2425(VerifyAgent1781): - pass - - -class VerificationItem1212(VerificationItem): - pass - - -class DeclaredBy1219(DeclaredBy): - pass - - -class VerifyAgent2426(VerifyAgent): - pass - - -class VerifyAgent2427(VerifyAgent1781): - pass - - -class VerificationItem1213(VerificationItem): - pass - - -class DeclaredBy1220(DeclaredBy): - pass - - -class VerifyAgent2428(VerifyAgent): - pass - - -class VerifyAgent2429(VerifyAgent1781): - pass - - -class VerificationItem1214(VerificationItem): - pass - - -class ReferenceAsset46(ReferenceAsset): - pass - - -class FeedFieldMapping49(FeedFieldMapping): - pass - - -class ReferenceAuthorization46(ReferenceAuthorization): - pass - - -class DeclaredBy1221(DeclaredBy): - pass - - -class VerifyAgent2430(VerifyAgent): - pass - - -class VerifyAgent2431(VerifyAgent1781): - pass - - -class VerificationItem1215(VerificationItem): - pass - - -class DeclaredBy1222(DeclaredBy): - pass - - -class VerifyAgent2432(VerifyAgent): - pass - - -class VerifyAgent2433(VerifyAgent1781): - pass - - -class VerificationItem1216(VerificationItem): - pass - - -class DeclaredBy1223(DeclaredBy): - pass - - -class VerifyAgent2434(VerifyAgent): - pass - - -class VerifyAgent2435(VerifyAgent1781): - pass - - -class VerificationItem1217(VerificationItem): - pass - - -class DeclaredBy1224(DeclaredBy): - pass - - -class VerifyAgent2436(VerifyAgent): - pass - - -class VerifyAgent2437(VerifyAgent1781): - pass - - -class VerificationItem1218(VerificationItem): - pass - - -class DeclaredBy1225(DeclaredBy): - pass - - -class VerifyAgent2438(VerifyAgent): - pass - - -class VerifyAgent2439(VerifyAgent1781): - pass - - -class VerificationItem1219(VerificationItem): - pass - - -class DeclaredBy1226(DeclaredBy): - pass - - -class VerifyAgent2440(VerifyAgent): - pass - - -class VerifyAgent2441(VerifyAgent1781): - pass - - -class VerificationItem1220(VerificationItem): - pass - - -class DeclaredBy1227(DeclaredBy): - pass - - -class VerifyAgent2442(VerifyAgent): - pass - - -class VerifyAgent2443(VerifyAgent1781): - pass - - -class VerificationItem1221(VerificationItem): - pass - - -class DeclaredBy1228(DeclaredBy): - pass - - -class VerifyAgent2444(VerifyAgent): - pass - - -class VerifyAgent2445(VerifyAgent1781): - pass - - -class VerificationItem1222(VerificationItem): - pass - - -class DeclaredBy1229(DeclaredBy): - pass - - -class VerifyAgent2446(VerifyAgent): - pass - - -class VerifyAgent2447(VerifyAgent1781): - pass - - -class VerificationItem1223(VerificationItem): - pass - - -class DeclaredBy1230(DeclaredBy): - pass - - -class VerifyAgent2448(VerifyAgent): - pass - - -class VerifyAgent2449(VerifyAgent1781): - pass - - -class VerificationItem1224(VerificationItem): - pass - - -class Feature(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="The feature that was evaluated (e.g., 'auto_redirect', 'brand_consistency'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:eu_ai_act_article_50'). Unprefixed feature IDs are agent-defined." - ), - ] - value: Annotated[ - bool | float | str, - Field( - description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of measurement for quantitative values (e.g., 'percentage', 'score')" - ), - ] = None - confidence: Annotated[ - float | None, Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0) - ] = None - measured_at: Annotated[ - AwareDatetime | None, Field(description='When this feature was evaluated') - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When this evaluation expires and should be refreshed'), - ] = None - methodology_version: Annotated[ - str | None, Field(description='Version of the methodology used to evaluate this feature') - ] = None - details: Annotated[ - dict[str, Any] | None, - Field(description='Additional vendor-specific details about this evaluation'), - ] = None - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Creative agents and sellers populate when the measurement was motivated by a specific policy; do NOT populate when the feature is a generic measurement (carbon score, brand consistency) unrelated to any policy. See /docs/governance/policy-attribution.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Eval(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - features: Annotated[ - list[Feature] | None, - Field( - description='Creative-feature values the evaluator measured for this leaf — `creative-feature-result[]` (e.g., a `creative_quality_score` number, an `on_brief` categorical, a calibrated `predicted_performance` in [0,1] from the exemplars form). The gate (evaluator.feature_requirement[]) and ranking (evaluator.rank_by) are evaluated over these values. Same shape get_creative_features returns.' - ), - ] = None - ranked_against: Annotated[ - int | None, - Field( - description='Number of leaves this leaf was scored against (the best-of-N N). Lets the buyer interpret rank in context.', - ge=1, - ), - ] = None - calls_used: Annotated[ - int | None, - Field( - description='Number of judge calls made during evaluation. Sellers SHOULD populate when agent_url was used and an eval_budget was supplied, giving buyers visibility into external call usage. No billing coupling in v1.', - ge=0, - ), - ] = None - seconds_used: Annotated[ - float | None, - Field( - description='Wall-clock seconds consumed during evaluation. Sellers SHOULD populate when agent_url was used and an eval_budget was supplied, giving buyers visibility into external call usage. No billing coupling in v1.', - ge=0.0, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue118(Issue): - pass - - -class Error54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue118] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class KeepModeApplied(StrEnum): - keep_all = 'keep_all' - keep_one = 'keep_one' - keep_some = 'keep_some' - - -class SelectionStrategyApplied(StrEnum): - audience_relevance = 'audience_relevance' - contextual_fit = 'contextual_fit' - performance = 'performance' - proximity = 'proximity' - inventory_priority = 'inventory_priority' - random = 'random' - - -class BudgetStatus(StrEnum): - complete = 'complete' - capped = 'capped' - - -class Issue119(Issue): - pass - - -class Error55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue119] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue120(Issue): - pass - - -class AdcpError57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue120] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Basis(StrEnum): - fixed = 'fixed' - estimated_units = 'estimated_units' - cpm_deferred = 'cpm_deferred' - - -class ConsumptionEstimate(Consumption): - pass - - -class PerLeafItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_item_ref: dict[str, Any] | None = None - variant_axis_value: Any | None = None - pricing_option_id: str | None = None - cost_low: Annotated[float | None, Field(ge=0.0)] = None - cost_high: Annotated[float | None, Field(ge=0.0)] = None - consumption_estimate: Annotated[ - ConsumptionEstimate | None, - Field( - description='Structured consumption details returned by build_creative when a paid creative agent computes cost. Contains well-known fields for common consumption metrics. The consumption object is informational — it lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth, not consumption.', - title='Creative Consumption', - ), - ] = None - - -class Estimate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - items_total: Annotated[ - int | None, - Field(description='Catalog items eligible (before max_creatives sampling).', ge=0), - ] = None - items_to_produce: Annotated[ - int | None, - Field(description='Distinct creatives that would be produced (after max_creatives).', ge=0), - ] = None - conditions_total: Annotated[ - int | None, - Field( - description='Signal-fan-out conditions axis count (len(signal_conditions)). Present when signal_conditions was sent so leaves_total = items_to_produce × conditions_total × variants_per_item is legible before spend.', - ge=1, - ), - ] = None - variants_per_item: Annotated[ - int | None, Field(description='Alternatives per creative (max_variants).', ge=1) - ] = None - leaves_total: Annotated[ - int | None, - Field( - description='Total billable leaves = items_to_produce × variants_per_item (× conditions_total when signal_conditions was sent — see conditions_total).', - ge=0, - ), - ] = None - currency: Annotated[ - str, Field(description='ISO 4217 currency for the cost band.', pattern='^[A-Z]{3}$') - ] - cost_low: Annotated[ - float, Field(description='Low end of the projected aggregate vendor_cost.', ge=0.0) - ] - cost_high: Annotated[ - float, - Field(description='High end. For basis "fixed" this equals cost_low (exact).', ge=0.0), - ] - cost_expected: Annotated[ - float | None, Field(description='Optional point estimate within the band.', ge=0.0) - ] = None - basis: Annotated[ - Basis, - Field( - description='`fixed` = per-format flat pricing (cost_low == cost_high, exact). `estimated_units` = generative per_unit where the seller projects a unit range (band reflects the uncertainty in seconds/images/tokens). `cpm_deferred` = CPM-priced; build-time cost is 0 and cost accrues at serve time (mirrors the vendor_cost:0 CPM convention).' - ), - ] - per_leaf: Annotated[ - list[PerLeafItem] | None, Field(description='Optional per-leaf breakdown of the estimate.') - ] = None - - -class Issue121(Issue): - pass - - -class AdcpError58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue121] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue122(Issue): - pass - - -class Error56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue122] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue123(Issue): - pass - - -class AdcpError59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue123] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue124(Issue): - pass - - -class Error57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue124] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason28(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' - ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' - - -class Issue125(Issue): - pass - - -class Error58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue125] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason28 | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[Error58] | None, - Field( - description='Optional validation errors or warnings explaining why input is required.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue126(Issue): - pass - - -class Error59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue126] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error59] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue127(Issue): - pass - - -class AdcpError60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue127] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class DeclaredBy1231(DeclaredBy): - pass - - -class VerifyAgent2450(VerifyAgent): - pass - - -class VerifyAgent2451(VerifyAgent1781): - pass - - -class VerificationItem1225(VerificationItem): - pass - - -class Contact17(Contact): - pass - - -class BillingEntity4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact17] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Action(StrEnum): - created = 'created' - updated = 'updated' - unchanged = 'unchanged' - failed = 'failed' - deleted = 'deleted' - - -class Status327(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class Issue128(Issue): - pass - - -class Error60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue128] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue129(Issue): - pass - - -class AdcpError61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue129] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue130(Issue): - pass - - -class Error61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue130] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue131(Issue): - pass - - -class AdcpError62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue131] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue132(Issue): - pass - - -class Error62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue132] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - creatives_processed: Annotated[ - int | None, Field(description='Number of creatives processed so far', ge=0) - ] = None - creatives_total: Annotated[ - int | None, Field(description='Total number of creatives to process', ge=0) - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason29(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' - FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' - - -class Result1300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason29 | None, Field(description='Reason code indicating why buyer input is needed') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue133(Issue): - pass - - -class Error63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue133] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error63] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue134(Issue): - pass - - -class AdcpError63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue134] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status328(StrEnum): - approved = 'approved' - pending = 'pending' - rejected = 'rejected' - warning = 'warning' - withdrawn = 'withdrawn' - - -class ItemIssue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - item_id: Annotated[str, Field(description='ID of the catalog item with an issue')] - status: Annotated[ - Status328, Field(description='Item review status', title='Catalog Item Status') - ] - reasons: Annotated[list[str] | None, Field(description='Reasons for rejection or warning')] = ( - None - ) - - -class Issue135(Issue): - pass - - -class Error64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue135] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[str, Field(description='Catalog ID from the request')] - action: Annotated[ - Action, Field(description='Action taken for this catalog', title='Catalog Action') - ] - platform_id: Annotated[ - str | None, Field(description='Platform-specific ID assigned to the catalog') - ] = None - item_count: Annotated[ - int | None, - Field( - description="Total number of items in the catalog after sync. Required when action is 'created', 'updated', or 'unchanged'. Omitted on 'failed' and 'deleted'.", - ge=0, - ), - ] = None - items_approved: Annotated[ - int | None, - Field( - description='Number of items approved by the platform. Populated when the platform performs item-level review.', - ge=0, - ), - ] = None - items_pending: Annotated[ - int | None, - Field( - description='Number of items pending platform review. Common for product catalogs where items must pass content policy checks.', - ge=0, - ), - ] = None - items_rejected: Annotated[ - int | None, - Field( - description='Number of items rejected by the platform. Check item_issues for rejection reasons.', - ge=0, - ), - ] = None - item_issues: Annotated[ - list[ItemIssue] | None, - Field( - description='Per-item issues reported by the platform (rejections, warnings). Only present when the platform performs item-level review.' - ), - ] = None - last_synced_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when the most recent sync was accepted by the platform' - ), - ] = None - next_fetch_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when the platform will next fetch the feed URL. Only present for URL-based catalogs with update_frequency.' - ), - ] = None - changes: Annotated[ - list[str] | None, - Field(description="Field names that were modified (only present when action='updated')"), - ] = None - errors: Annotated[ - list[Error64] | None, - Field(description="Validation or processing errors (only present when action='failed')"), - ] = None - warnings: Annotated[ - list[str] | None, Field(description='Non-fatal warnings about this catalog') - ] = None - - -class Issue136(Issue): - pass - - -class AdcpError64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue136] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue137(Issue): - pass - - -class Error65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue137] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue138(Issue): - pass - - -class AdcpError65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue138] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue139(Issue): - pass - - -class Error66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue139] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - catalogs_processed: Annotated[ - int | None, Field(description='Number of catalogs processed so far', ge=0) - ] = None - catalogs_total: Annotated[ - int | None, Field(description='Total number of catalogs to process', ge=0) - ] = None - items_processed: Annotated[ - int | None, - Field(description='Total number of catalog items processed across all catalogs', ge=0), - ] = None - items_total: Annotated[ - int | None, - Field(description='Total number of catalog items to process across all catalogs', ge=0), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason30(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - FEED_VALIDATION = 'FEED_VALIDATION' - ITEM_REVIEW = 'ITEM_REVIEW' - FEED_ACCESS = 'FEED_ACCESS' - - -class Result1306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason30 | None, - Field( - description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue140(Issue): - pass - - -class Error67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue140] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result1307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error67] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class AdCPProtocol(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - governance = 'governance' - creative = 'creative' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - measurement = 'measurement' - - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' - - -class VideoPlacementType(StrEnum): - instream = 'instream' - accompanying_content = 'accompanying_content' - interstitial = 'interstitial' - standalone = 'standalone' - - -class AudioDistributionType(StrEnum): - music_streaming_service = 'music_streaming_service' - fm_am_broadcast = 'fm_am_broadcast' - podcast = 'podcast' - catch_up_radio = 'catch_up_radio' - web_radio = 'web_radio' - video_game = 'video_game' - text_to_speech = 'text_to_speech' - - -class SponsoredPlacementType(StrEnum): - sponsored_search = 'sponsored_search' - sponsored_display = 'sponsored_display' - sponsored_native = 'sponsored_native' - - -class SocialPlacementSurface(StrEnum): - feed = 'feed' - stories = 'stories' - short_video = 'short_video' - explore = 'explore' - search = 'search' - - -class DeliveryType(StrEnum): - guaranteed = 'guaranteed' - non_guaranteed = 'non_guaranteed' - - -class Exclusivity(StrEnum): - none = 'none' - category = 'category' - exclusive = 'exclusive' - - -class PriceAdjustmentKind(StrEnum): - fee = 'fee' - discount = 'discount' - commission = 'commission' - settlement = 'settlement' - - -class DemographicSystem(StrEnum): - nielsen = 'nielsen' - barb = 'barb' - agf = 'agf' - oztam = 'oztam' - mediametrie = 'mediametrie' - custom = 'custom' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class GeographicTargetingLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class ForecastRangeUnit(StrEnum): - spend = 'spend' - availability = 'availability' - reach_freq = 'reach_freq' - weekly = 'weekly' - daily = 'daily' - clicks = 'clicks' - conversions = 'conversions' - package = 'package' - - -class ForecastMethod(StrEnum): - estimate = 'estimate' - modeled = 'modeled' - guaranteed = 'guaranteed' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class MakegoodRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class PerformanceStandardMetric(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' - - -class MediaBuyActionMode(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - requires_approval = 'requires_approval' - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class CoBrandingRequirement(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' - - -class LandingPageRequirement(StrEnum): - any = 'any' - retailer_site_only = 'retailer_site_only' - must_include_retailer = 'must_include_retailer' - - -class SignalValueType(StrEnum): - binary = 'binary' - categorical = 'categorical' - numeric = 'numeric' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class AssessmentStatus(StrEnum): - insufficient = 'insufficient' - minimum = 'minimum' - good = 'good' - excellent = 'excellent' - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class InstallmentStatus(StrEnum): - scheduled = 'scheduled' - tentative = 'tentative' - live = 'live' - postponed = 'postponed' - cancelled = 'cancelled' - aired = 'aired' - published = 'published' - - -class ContentRatingSystem(StrEnum): - tv_parental = 'tv_parental' - mpaa = 'mpaa' - podcast = 'podcast' - esrb = 'esrb' - bbfc = 'bbfc' - fsk = 'fsk' - acb = 'acb' - chvrs = 'chvrs' - csa = 'csa' - pegi = 'pegi' - custom = 'custom' - - -class SpecialCategory(StrEnum): - awards = 'awards' - championship = 'championship' - concert = 'concert' - conference = 'conference' - election = 'election' - festival = 'festival' - gala = 'gala' - holiday = 'holiday' - premiere = 'premiere' - product_launch = 'product_launch' - reunion = 'reunion' - tribute = 'tribute' - - -class TalentRole(StrEnum): - host = 'host' - guest = 'guest' - creator = 'creator' - cast = 'cast' - narrator = 'narrator' - producer = 'producer' - correspondent = 'correspondent' - commentator = 'commentator' - analyst = 'analyst' - - -class DerivativeType(StrEnum): - clip = 'clip' - highlight = 'highlight' - recap = 'recap' - trailer = 'trailer' - bonus = 'bonus' - - -class TMPResponseType(StrEnum): - activation = 'activation' - catalog_items = 'catalog_items' - creative = 'creative' - deal = 'deal' - - -class UIDType(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' - - -class DayOfWeek(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class AccountStatus(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' - - -class BillingParty(StrEnum): - operator = 'operator' - agent = 'agent' - advertiser = 'advertiser' - - -class PaymentTerms(StrEnum): - net_15 = 'net_15' - net_30 = 'net_30' - net_45 = 'net_45' - net_60 = 'net_60' - net_90 = 'net_90' - prepay = 'prepay' - - -class AccountScope(StrEnum): - operator = 'operator' - brand = 'brand' - operator_brand = 'operator_brand' - agent = 'agent' - - -class CloudStorageProtocol(StrEnum): - s3 = 's3' - gcs = 'gcs' - azure_blob = 'azure_blob' - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - impairment = 'impairment' - creative_status_changed = 'creative.status_changed' - creative_purged = 'creative.purged' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class AgeVerificationMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class TravelTimeUnit(StrEnum): - min = 'min' - hr = 'hr' - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class DistanceUnit(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class AttributionModel(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class CanceledBy(StrEnum): - buyer = 'buyer' - seller = 'seller' - - -class PricingModel(StrEnum): - cpm = 'cpm' - vcpm = 'vcpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpv = 'cpv' - cpp = 'cpp' - cpa = 'cpa' - flat_rate = 'flat_rate' - time = 'time' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class RightUse(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class DataProviderSignalSelector7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all signals from this data provider are included' - ), - ] = 'all' - - -class SignalId103(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-zA-Z0-9_-]+$')] - - -class DataProviderSignalSelector8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific signal IDs'), - ] = 'by_id' - signal_ids: Annotated[ - list[SignalId103], - Field( - description="Specific signal IDs from the data provider's published signal definitions", - min_length=1, - ), - ] - - -class SignalTag(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9_-]+$')] - - -class DataProviderSignalSelector9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by signal tags') - ] = 'by_tag' - signal_tags: Annotated[ - list[SignalTag], - Field( - description="Signal tags from the data provider's published signal definitions. Selector covers all signals with these tags", - min_length=1, - ), - ] - - -class DataProviderSignalSelector( - RootModel[ - DataProviderSignalSelector7 | DataProviderSignalSelector8 | DataProviderSignalSelector9 - ] -): - root: Annotated[ - DataProviderSignalSelector7 | DataProviderSignalSelector8 | DataProviderSignalSelector9, - Field( - description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", - discriminator='selection_type', - title='Data Provider Signal Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Details(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: AdCPProtocol | None = None - operation: Annotated[str | None, Field(description='Specific operation that failed')] = None - specific_context: Annotated[ - dict[str, Any] | None, Field(description='Domain-specific error context') - ] = None - - -class Error41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[str, Field(description='Error code for programmatic handling')] - message: Annotated[str, Field(description='Detailed error message')] - details: Annotated[Details | None, Field(description='Additional error context')] = None - - -class PushNotificationConfig49(PushNotificationConfig): - pass - - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType, - Field( - description="Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." - ), - ] - required: Annotated[ - bool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - int | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - int | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - int | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - int | None, - Field( - description="Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot122(Slot): - pass - - -class Params122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot122] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection121] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions115(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params122, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot123(Slot): - pass - - -class Params123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot123] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection122] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions116(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params123, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot124(Slot): - pass - - -class Params124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot124] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection123] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions117(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params124, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot125(Slot): - pass - - -class Params125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot125] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection124] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions118(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params125, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot126(Slot): - pass - - -class Params126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot126] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection125] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions119(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params126, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot127(Slot): - pass - - -class Params127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot127] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection126] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions120(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params127, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot128(Slot): - pass - - -class Params128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot128] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection127] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions121(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params128, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot129(Slot): - pass - - -class Params129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot129] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection128] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions122(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params129, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot130(Slot): - pass - - -class Params130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot130] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection129] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions123(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params130, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot131(Slot): - pass - - -class Params131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot131] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection130] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions124(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params131, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot132(Slot): - pass - - -class Params132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot132] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection131] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions125(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params132, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions126(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class FormatOptions113( - RootModel[ - FormatOptions - | FormatOptions115 - | FormatOptions116 - | FormatOptions117 - | FormatOptions118 - | FormatOptions119 - | FormatOptions120 - | FormatOptions121 - | FormatOptions122 - | FormatOptions123 - | FormatOptions124 - | FormatOptions125 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions - | FormatOptions115 - | FormatOptions116 - | FormatOptions117 - | FormatOptions118 - | FormatOptions119 - | FormatOptions120 - | FormatOptions121 - | FormatOptions122 - | FormatOptions123 - | FormatOptions124 - | FormatOptions125 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot133(Slot): - pass - - -class Params133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot133] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection132] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions128(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params133, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot134(Slot): - pass - - -class Params134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot134] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection133] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions129(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params134, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot135(Slot): - pass - - -class Params135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot135] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection134] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions130(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params135, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot136(Slot): - pass - - -class Params136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot136] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection135] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions131(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params136, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot137(Slot): - pass - - -class Params137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot137] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection136] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions132(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params137, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot138(Slot): - pass - - -class Params138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot138] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection137] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions133(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params138, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot139(Slot): - pass - - -class Params139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot139] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection138] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions134(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params139, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot140(Slot): - pass - - -class Params140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot140] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection139] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions135(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params140, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot141(Slot): - pass - - -class Params141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot141] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection140] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions136(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params141, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot142(Slot): - pass - - -class Params142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot142] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection141] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions137(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params142, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot143(Slot): - pass - - -class Params143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot143] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection142] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions138(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params143, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot144(Slot): - pass - - -class Params144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot144] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection143] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions139(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params144, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions127( - RootModel[ - FormatOptions128 - | FormatOptions129 - | FormatOptions130 - | FormatOptions131 - | FormatOptions132 - | FormatOptions133 - | FormatOptions134 - | FormatOptions135 - | FormatOptions136 - | FormatOptions137 - | FormatOptions138 - | FormatOptions139 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions128 - | FormatOptions129 - | FormatOptions130 - | FormatOptions131 - | FormatOptions132 - | FormatOptions133 - | FormatOptions134 - | FormatOptions135 - | FormatOptions136 - | FormatOptions137 - | FormatOptions138 - | FormatOptions139 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - Sequence[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions127] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class Adjustment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: PriceAdjustmentKind - name: Annotated[ - str, - Field( - description='Specific adjustment name. Use well-known values where applicable for interoperability.', - examples=[ - 'ad_serving', - 'data_targeting', - 'brand_safety', - 'volume', - 'negotiated', - 'early_booking', - 'agency', - 'intermediary', - 'cash_discount', - 'early_payment', - ], - max_length=64, - ), - ] - rate: Annotated[ - float | None, - Field( - description='Adjustment as a decimal proportion (e.g., 0.15 for 15%). Always positive — kind determines the economic effect. Mutually exclusive with amount.', - gt=0.0, - lt=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Adjustment as a fixed monetary amount in the pricing option's currency. Always positive — kind determines the economic effect. Mutually exclusive with rate.", - gt=0.0, - ), - ] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this adjustment (e.g., 'Malstaffel 12x', '2% Skonto 10 Tage')", - max_length=256, - ), - ] = None - beneficiary: Annotated[ - str | None, - Field( - description="Identifies who receives this adjustment's value. For commissions, the intermediary (e.g., a sellers.json domain, an AdCP account ID, or a human-readable party name). Optional but recommended for multi-intermediary transparency.", - max_length=256, - ), - ] = None - - -class PriceBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - list_price: Annotated[ - float, - Field( - description='Rate card or base price before any adjustments. The starting point from which fixed_price is derived by applying fee and discount adjustments sequentially.', - gt=0.0, - ), - ] - adjustments: Annotated[ - list[Adjustment], - Field( - description="Ordered list of price adjustments. Fee and discount adjustments walk list_price to fixed_price — fees increase the running price, discounts reduce it. Commission and settlement adjustments are disclosed for transparency but do not affect the buyer's committed price.", - max_length=20, - min_length=1, - ), - ] - - -class PricingOptions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown40(PriceBreakdown): - pass - - -class PricingOptions42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown40 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown41(PriceBreakdown): - pass - - -class PricingOptions43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown41 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown42(PriceBreakdown): - pass - - -class PricingOptions44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown42 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown43(PriceBreakdown): - pass - - -class PricingOptions45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown43 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class Parameters20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str, - Field( - description='Target demographic code within the specified demographic_system (e.g., P18-49 for Nielsen, ABC1 Adults for BARB)' - ), - ] - min_points: Annotated[float | None, Field(description='Minimum GRPs/TRPs required', ge=0.0)] = ( - None - ) - - -class PriceBreakdown44(PriceBreakdown): - pass - - -class PricingOptions46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters20, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown44 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown45(PriceBreakdown): - pass - - -class PricingOptions47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown45 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown46(PriceBreakdown): - pass - - -class PricingOptions48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters21 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown46 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown47(PriceBreakdown): - pass - - -class PricingOptions49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters22, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown47 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions40( - RootModel[ - PricingOptions - | PricingOptions42 - | PricingOptions43 - | PricingOptions44 - | PricingOptions45 - | PricingOptions46 - | PricingOptions47 - | PricingOptions48 - | PricingOptions49 - ] -): - root: Annotated[ - PricingOptions - | PricingOptions42 - | PricingOptions43 - | PricingOptions44 - | PricingOptions45 - | PricingOptions46 - | PricingOptions47 - | PricingOptions48 - | PricingOptions49, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions61(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' - geo_level: GeographicTargetingLevel - system: Annotated[ - str | None, - Field( - description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." - ), - ] = None - - -class Dimensions63(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' - device_type: DeviceType - - -class Dimensions64(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['device_platform'], Field(description='Dimension family discriminator.') - ] = 'device_platform' - device_platform: DevicePlatform - - -class Dimensions65(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' - audience_id: Annotated[ - str, Field(description='Audience segment identifier for this forecast row.') - ] - audience_source: AudienceSource - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name.') - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1781 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction934(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure892(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction934] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure892 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem891(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1782 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark891(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1783 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction935(Jurisdiction934): - pass - - -class Disclosure893(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction935] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance890(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy897 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem891] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark891] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure893 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem891] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance890 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo101 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor53(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride100 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor53, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[Dimensions61 | Dimensions | Dimensions63 | Dimensions64 | Dimensions65 | Dimensions66] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem892(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1784 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark892(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1785 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction936(Jurisdiction934): - pass - - -class Disclosure894(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction936] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance891(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy898 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem892] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark892] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure894 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem892] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance891 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo102 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor54(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride101 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement(AdCPBaseModel): - vendors: Annotated[ - list[Vendor54] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem893(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1786 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark893(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1787 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction937(Jurisdiction934): - pass - - -class Disclosure895(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction937] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance892(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy899 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem893] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark893] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure895 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem893] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance892 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo103 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor55(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride102 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor55, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[MakegoodRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem894(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1788 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark894(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1789 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction938(Jurisdiction934): - pass - - -class Disclosure896(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction938] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance893(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy900 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem894] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark894] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure896 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem894] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance893 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo104 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor56(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride103 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor56, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: MediaBuyValidAction - modes: Annotated[ - list[MediaBuyActionMode], - Field( - description='Modes available for this action on this product. A product may declare multiple modes (for example `self_serve` within tolerances, escalating to `requires_approval` outside) — the buy-side `available_actions[].mode` resolves to the singular mode in effect at mutation time. SDKs that see multiple modes MUST NOT assume which one will fire; they must read the resolved `mode` on the buy.', - min_length=1, - ), - ] - allowed_statuses: Annotated[ - list[MediaBuyStatus] | None, - Field( - description='Media buy statuses in which this action is permitted. When absent, the action is permitted in all non-terminal statuses (`pending_creatives`, `pending_start`, `active`, `paused`).', - min_length=1, - ), - ] = None - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this product. Absence means no commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). When present, the named term governs cancellation policy, makegoods, or other commercial remedies tied to this action. Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class EmbeddedProvenanceItem895(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1790 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark895(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1791 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction939(Jurisdiction934): - pass - - -class Disclosure897(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction939] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance894(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy901 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem895] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark895] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure897 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem895] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance894 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo105 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor57(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride104 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor57, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - co_branding: CoBrandingRequirement - landing_page: LandingPageRequirement - templates_available: Annotated[ - bool, Field(description='Whether creative templates are provided') - ] - provenance_required: Annotated[ - bool | None, - Field( - description='Whether creatives must include provenance metadata. When true, the seller requires buyers to attach provenance declarations to creative submissions. The seller may independently verify claims via get_creative_features.' - ), - ] = None - provenance_requirements: Annotated[ - ProvenanceRequirements | None, - Field( - description='Structured provenance requirements for creatives. Refines `provenance_required`: when `provenance_required` is true, the fields in this object specify which provenance features the seller requires. When `provenance_required` is false or absent, this object SHOULD be absent; if present, receivers MUST ignore it. Existing seller agents that do not read this object are unaffected; the wire shape does not change for them. Sellers that publish a requirement here MUST enforce it on creative submission: a `sync_creatives` request that omits a required field is rejected with the corresponding `PROVENANCE_*` error code (see error-code.json), and a creative whose provenance claim is contradicted by an independent verification (`get_creative_features` against a governance agent the seller operates or has allowlisted via `accepted_verifiers`) is rejected with `PROVENANCE_CLAIM_CONTRADICTED`. This is the structural-rejection surface; the truth-of-claim surface lives in `get_creative_features`. Field-level requirements are seller-enforced — JSON Schema validation does not check them.' - ), - ] = None - accepted_verifiers: Annotated[ - list[AcceptedVerifier] | None, - Field( - description='Governance agents the seller operates, has allowlisted, or otherwise trusts to verify provenance claims via `get_creative_features`. Buyers attaching a `verify_agent` pointer on `embedded_provenance[]` or `watermarks[]` MUST select an `agent_url` that appears in this list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) - the buyer is *representing* that they used a verifier the seller will recognize, not asserting unilateral routing. Sellers MUST reject `sync_creatives` submissions whose `verify_agent.agent_url` does not match any entry here with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call. Publishing the list lets buyers pre-flight their creative shape against `get_products` and lets multiple buyers converge on the same verifier without coordinating with each other.', - min_length=1, - ), - ] = None - - -class IncludedSignal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric], - Field( - description='Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here. **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — declare vendor-attested attention/quality metrics via `vendor_metric_optimization.supported_metrics[]` with an explicit vendor binding instead. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind.', - min_length=1, - ), - ] - supported_reach_units: Annotated[ - list[ReachUnit] | None, - Field( - description="Reach units this product can optimize for. Required when supported_metrics includes 'reach'. Buyers must set reach_unit to a value in this list on reach optimization goals — sellers reject unsupported values.", - min_length=1, - ), - ] = None - supported_view_durations: Annotated[ - list[SupportedViewDuration] | None, - Field( - description="Video view duration thresholds (in seconds) this product supports for completed_views goals. Only relevant when supported_metrics includes 'completed_views'. When absent, the seller uses their platform default. Buyers must set view_duration_seconds to a value in this list — sellers reject unsupported values." - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for metric goals on this product. Values match target.kind on the optimization goal. Only these target kinds are accepted — goals with unlisted target kinds will be rejected. When omitted, buyers can set target-less metric goals (maximize volume within budget) but cannot set specific targets.' - ), - ] = None - - -class EmbeddedProvenanceItem896(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1792 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark896(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1793 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction940(Jurisdiction934): - pass - - -class Disclosure898(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction940] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance895(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy902 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem896] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark896] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure898 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem896] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance895 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo106 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor58(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride105 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor58, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric10], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue90] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_sources: Annotated[ - list[ActionSource] | None, - Field( - description="Action sources relevant to this product (e.g. a retail media product might have 'in_store' and 'website', while a display product might only have 'website')", - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget20] | None, - Field( - description='Target kinds available for event goals on this product. Values match target.kind on the optimization goal. cost_per: target cost per conversion event. per_ad_spend: target return on ad spend (requires value_field on event sources). maximize_value: maximize total conversion value without a specific ratio target (requires value_field). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes conversion count within budget — no declaration needed for that mode. When omitted, buyers can still set target-less event goals.', - min_length=1, - ), - ] = None - platform_managed: Annotated[ - bool | None, - Field( - description="Whether the seller provides its own always-on measurement (e.g. Amazon sales attribution for Amazon advertisers). When true, sync_event_sources response will include seller-managed event sources with managed_by='seller'." - ), - ] = None - - -class EmbeddedProvenanceItem897(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1794 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark897(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1795 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction941(Jurisdiction934): - pass - - -class Disclosure899(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction941] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance896(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy903 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem897] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark897] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure899 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem897] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance896 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem898(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1796 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark898(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1797 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction942(Jurisdiction934): - pass - - -class Disclosure900(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction942] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance897(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy904 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem898] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark898] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure900 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem898] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance897 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem899(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1798 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark899(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1799 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction943(Jurisdiction934): - pass - - -class Disclosure901(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction943] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance898(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy905 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem899] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark899] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure901 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem899] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance898 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class ContentRating(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: ContentRatingSystem - rating: Annotated[ - str, Field(description="Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") - ] - - -class Special(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, Field(description="Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')") - ] - category: SpecialCategory | None = None - starts: Annotated[ - AwareDatetime | None, Field(description='When the event starts (ISO 8601)') - ] = None - ends: Annotated[ - AwareDatetime | None, - Field(description='When the event ends (ISO 8601). Omit for single-day events.'), - ] = None - - -class GuestTalentItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: TalentRole - name: Annotated[str, Field(description="Person's name as credited on the collection")] - brand_url: Annotated[ - AnyUrl | None, - Field( - description="URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations." - ), - ] = None - - -class DerivativeOf(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - installment_id: Annotated[ - str, Field(description='The source installment this content is derived from') - ] - type: DerivativeType - - -class Installment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="Provider's agent URL from the registry. Canonical identifier for this TMP provider." - ), - ] - context_match: Annotated[ - bool | None, - Field(description='Whether this provider handles context match for this product.'), - ] = False - identity_match: Annotated[ - bool | None, - Field(description='Whether this provider handles identity match for this product.'), - ] = False - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves for identity match. The router uses this to select the correct regional provider based on the request's country field. Required when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[UIDType] | None, - Field( - description="Identity types this regional provider can resolve. The router filters providers whose uid_types includes the request's uid_type. Required when identity_match is true.", - min_length=1, - ), - ] = None - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class Products(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId], - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] - format_options: Annotated[ - list[FormatOptions113] | None, - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[Placement] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions40], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot145(Slot): - pass - - -class Params145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot145] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection144] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions142(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params145, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot146(Slot): - pass - - -class Params146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot146] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection145] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions143(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params146, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot147(Slot): - pass - - -class Params147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot147] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection146] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions144(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params147, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot148(Slot): - pass - - -class Params148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot148] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection147] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions145(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params148, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot149(Slot): - pass - - -class Params149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot149] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection148] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions146(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params149, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot150(Slot): - pass - - -class Params150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot150] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection149] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions147(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params150, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot151(Slot): - pass - - -class Params151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot151] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection150] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions148(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params151, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot152(Slot): - pass - - -class Params152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot152] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection151] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions149(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params152, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot153(Slot): - pass - - -class Params153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot153] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection152] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions150(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params153, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot154(Slot): - pass - - -class Params154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot154] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection153] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions151(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params154, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot155(Slot): - pass - - -class Params155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot155] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection154] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions152(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params155, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot156(Slot): - pass - - -class Params156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot156] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection155] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions153(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params156, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions141( - RootModel[ - FormatOptions142 - | FormatOptions143 - | FormatOptions144 - | FormatOptions145 - | FormatOptions146 - | FormatOptions147 - | FormatOptions148 - | FormatOptions149 - | FormatOptions150 - | FormatOptions151 - | FormatOptions152 - | FormatOptions153 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions142 - | FormatOptions143 - | FormatOptions144 - | FormatOptions145 - | FormatOptions146 - | FormatOptions147 - | FormatOptions148 - | FormatOptions149 - | FormatOptions150 - | FormatOptions151 - | FormatOptions152 - | FormatOptions153 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot157(Slot): - pass - - -class Params157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot157] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection156] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions156(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params157, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot158(Slot): - pass - - -class Params158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot158] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection157] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions157(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params158, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot159(Slot): - pass - - -class Params159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot159] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection158] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions158(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params159, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot160(Slot): - pass - - -class Params160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot160] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection159] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions159(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params160, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot161(Slot): - pass - - -class Params161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot161] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection160] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions160(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params161, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot162(Slot): - pass - - -class Params162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot162] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection161] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions161(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params162, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot163(Slot): - pass - - -class Params163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot163] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection162] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions162(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params163, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot164(Slot): - pass - - -class Params164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot164] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection163] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions163(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params164, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot165(Slot): - pass - - -class Params165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot165] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection164] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions164(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params165, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot166(Slot): - pass - - -class Params166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot166] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection165] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions165(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params166, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot167(Slot): - pass - - -class Params167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot167] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection166] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions166(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params167, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot168(Slot): - pass - - -class Params168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot168] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection167] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions167(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params168, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions155( - RootModel[ - FormatOptions156 - | FormatOptions157 - | FormatOptions158 - | FormatOptions159 - | FormatOptions160 - | FormatOptions161 - | FormatOptions162 - | FormatOptions163 - | FormatOptions164 - | FormatOptions165 - | FormatOptions166 - | FormatOptions167 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions156 - | FormatOptions157 - | FormatOptions158 - | FormatOptions159 - | FormatOptions160 - | FormatOptions161 - | FormatOptions162 - | FormatOptions163 - | FormatOptions164 - | FormatOptions165 - | FormatOptions166 - | FormatOptions167 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions155] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown48(PriceBreakdown): - pass - - -class PricingOptions51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown48 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown49(PriceBreakdown): - pass - - -class PricingOptions52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown49 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown50(PriceBreakdown): - pass - - -class PricingOptions53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown50 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown51(PriceBreakdown): - pass - - -class PricingOptions54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown51 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown52(PriceBreakdown): - pass - - -class PricingOptions55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters23, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown52 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown53(PriceBreakdown): - pass - - -class PricingOptions56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters20, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown53 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown54(PriceBreakdown): - pass - - -class PricingOptions57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown54 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown55(PriceBreakdown): - pass - - -class PricingOptions58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters25 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown55 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown56(PriceBreakdown): - pass - - -class PricingOptions59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters26, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown56 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions50( - RootModel[ - PricingOptions51 - | PricingOptions52 - | PricingOptions53 - | PricingOptions54 - | PricingOptions55 - | PricingOptions56 - | PricingOptions57 - | PricingOptions58 - | PricingOptions59 - ] -): - root: Annotated[ - PricingOptions51 - | PricingOptions52 - | PricingOptions53 - | PricingOptions54 - | PricingOptions55 - | PricingOptions56 - | PricingOptions57 - | PricingOptions58 - | PricingOptions59, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions67(Dimensions61): - pass - - -class Dimensions69(Dimensions63): - pass - - -class Dimensions70(Dimensions64): - pass - - -class Dimensions71(Dimensions65): - pass - - -class EmbeddedProvenanceItem900(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1800 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark900(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1801 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction944(Jurisdiction934): - pass - - -class Disclosure902(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction944] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance899(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy906 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem900] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark900] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure902 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem900] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance899 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo107 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor59(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride106 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor59 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem901(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1802 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark901(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1803 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction945(Jurisdiction934): - pass - - -class Disclosure903(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction945] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance900(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy907 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem901] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark901] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure903 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem901] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance900 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo108 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor60(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride107 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor60, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions67 | Dimensions68 | Dimensions69 | Dimensions70 | Dimensions71 | Dimensions72 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics11, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability16 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue14] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point10], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem902(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1804 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark902(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1805 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction946(Jurisdiction934): - pass - - -class Disclosure904(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction946] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance901(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy908 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem902] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark902] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure904 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem902] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance901 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo109 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor61(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride108 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement6(AdCPBaseModel): - vendors: Annotated[ - list[Vendor61] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem903(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1806 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark903(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1807 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction947(Jurisdiction934): - pass - - -class Disclosure905(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction947] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance902(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy909 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem903] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark903] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure905 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem903] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance902 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo110 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor62(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride109 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor62, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement8 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem904(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1808 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark904(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1809 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction948(Jurisdiction934): - pass - - -class Disclosure906(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction948] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance903(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy910 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem904] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark904] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure906 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem904] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance903 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo111 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor63(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride110 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor63, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction5(AllowedAction): - pass - - -class EmbeddedProvenanceItem905(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1810 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark905(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1811 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction949(Jurisdiction934): - pass - - -class Disclosure907(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction949] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance904(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy911 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem905] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark905] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure907 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem905] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance904 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo112 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor64(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride111 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor64, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric6] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown5 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy6(CreativePolicy): - pass - - -class IncludedSignal5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption24] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization6(MetricOptimization): - pass - - -class EmbeddedProvenanceItem906(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1812 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark906(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1813 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction950(Jurisdiction934): - pass - - -class Disclosure908(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction950] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance905(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy912 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem906] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark906] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure908 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem906] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance905 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo113 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor65(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride112 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor65, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric12], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue91] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking8(ConversionTracking): - pass - - -class EmbeddedProvenanceItem907(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1814 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark907(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1815 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction951(Jurisdiction934): - pass - - -class Disclosure909(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction951] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance906(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy913 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem907] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark907] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure909 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem907] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance906 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image6 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem908(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1816 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark908(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1817 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction952(Jurisdiction934): - pass - - -class Disclosure910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction952] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance907(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy914 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem908] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark908] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure910 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem908] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance907 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem909(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1818 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark909(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1819 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction953(Jurisdiction934): - pass - - -class Disclosure911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction953] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance908(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy915 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem909] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark909] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure911 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem909] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance908 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage5 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage5] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines5 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider13(Provider): - pass - - -class TrustedMatch9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider13] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class Products3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty13], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] = None - format_options: Annotated[ - list[FormatOptions141], - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] - placements: Annotated[ - list[Placement6] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions50], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast7 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement5 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement6 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms7 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard7] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy6 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction5] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities5, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy6 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal5] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption5] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules5 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization6 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization7 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness5 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking8 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch6 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard7 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed6 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment5] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch9 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[DayOfWeek], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Dimensions73(Dimensions61): - pass - - -class Dimensions75(Dimensions63): - pass - - -class Dimensions76(Dimensions64): - pass - - -class Dimensions77(Dimensions65): - pass - - -class EmbeddedProvenanceItem910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1820 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1821 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction954(Jurisdiction934): - pass - - -class Disclosure912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction954] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance909(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy916 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem910] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark910] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure912 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem910] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance909 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo114 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor66(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride113 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor66 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1822 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1823 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction955(Jurisdiction934): - pass - - -class Disclosure913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction955] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy917 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem911] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark911] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure913 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem911] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance910 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo115 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor67(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride114 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor67, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions73 | Dimensions74 | Dimensions75 | Dimensions76 | Dimensions77 | Dimensions78 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics12, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability17 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue15] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point11], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Allocation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, Field(description='ID of the product (must reference a product in the products array)') - ] - allocation_percentage: Annotated[ - float, - Field( - description='Percentage of total budget allocated to this product (0-100)', - ge=0.0, - le=100.0, - ), - ] - pricing_option_id: Annotated[ - str | None, - Field(description="Recommended pricing option ID from the product's pricing_options array"), - ] = None - rationale: Annotated[ - str | None, - Field(description='Explanation of why this product and allocation are recommended'), - ] = None - sequence: Annotated[ - int | None, - Field(description='Optional ordering hint for multi-line-item plans (1-based)', ge=1), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Categorical tags for this allocation (e.g., 'desktop', 'german', 'mobile') - useful for grouping/filtering allocations by dimension" - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight start date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight end date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Recommended time windows for this allocation in spot-plan proposals.', - min_length=1, - ), - ] = None - forecast: Annotated[ - Forecast8 | None, - Field( - description='Forecasted delivery metrics for this allocation', title='Delivery Forecast' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Dimensions79(Dimensions61): - pass - - -class Dimensions81(Dimensions63): - pass - - -class Dimensions82(Dimensions64): - pass - - -class Dimensions83(Dimensions65): - pass - - -class EmbeddedProvenanceItem912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1824 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1825 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction956(Jurisdiction934): - pass - - -class Disclosure914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction956] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy918 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem912] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark912] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure914 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem912] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance911 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo116 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor68(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride115 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor68 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1826 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1827 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction957(Jurisdiction934): - pass - - -class Disclosure915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction957] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy919 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem913] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark913] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure915 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem913] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance912 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo117 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor69(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride116 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor69, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions79 | Dimensions80 | Dimensions81 | Dimensions82 | Dimensions83 | Dimensions84 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics13, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability18 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue16] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point12], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Proposal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - proposal_id: Annotated[ - str, - Field( - description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', - max_length=255, - ), - ] - name: Annotated[ - str, Field(description='Human-readable name for this media plan proposal', max_length=500) - ] - description: Annotated[ - str | None, - Field( - description='Explanation of the proposal strategy and what it achieves', max_length=2000 - ), - ] = None - allocations: Annotated[ - list[Allocation], - Field( - description='Budget allocations across products. Allocation percentages MUST sum to 100. Publishers are responsible for ensuring the sum equals 100; buyers SHOULD validate this before execution.', - min_length=1, - ), - ] - proposal_status: Annotated[ - ProposalStatus | None, - Field( - description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy.", - title='Proposal Status', - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' - ), - ] = None - insertion_order: Annotated[ - InsertionOrder | None, - Field( - description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.', - title='Insertion Order', - ), - ] = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') - ] = None - brief_alignment: Annotated[ - str | None, - Field( - description='Explanation of how this proposal aligns with the campaign brief', - max_length=2000, - ), - ] = None - forecast: Annotated[ - Forecast9 | None, - Field( - description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.', - title='Delivery Forecast', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result931(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError46 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig49 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - products: Annotated[ - list[Products | Products3] | None, Field(description='Array of matching products') - ] = None - extensions: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, - Field( - description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `@` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' - ), - ] = None - proposals: Annotated[ - list[Proposal] | None, - Field( - description='Optional array of proposed media plans with budget allocations across products. Publishers include proposals when they can provide strategic guidance based on the brief. Proposals are actionable - buyers can refine them via follow-up get_products calls within the same session, or execute them directly via create_media_buy.' - ), - ] = None - errors: Annotated[ - list[Error] | None, - Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), - ] = None - property_list_applied: Annotated[ - bool | None, - Field( - description='[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent.' - ), - ] = None - catalog_applied: Annotated[ - bool | None, - Field( - description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' - ), - ] = None - refinement_applied: Annotated[ - list[RefinementApplied8] | None, - Field( - description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - filter_diagnostics: Annotated[ - FilterDiagnostics | None, - Field( - description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", - examples=[ - { - 'semantics': 'only', - 'total_candidates': 47, - 'excluded_by': { - 'required_metrics': {'count': 31, 'values': ['completed_views']}, - 'required_geo_targeting': {'count': 9}, - 'pricing_currencies': {'count': 3, 'values': ['USD']}, - 'budget_range': {'count': 7}, - }, - } - ], - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under (agent, buying_mode, filters, property_list, catalog, account_id). When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay for the (agent, filters, mode) tuple. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 sellers correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = None - unchanged: Annotated[ - Literal[True], - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry." - ), - ] = True - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot169(Slot): - pass - - -class Params169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot169] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection168] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions170(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params169, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot170(Slot): - pass - - -class Params170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot170] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection169] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions171(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params170, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot171(Slot): - pass - - -class Params171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot171] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection170] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions172(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params171, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot172(Slot): - pass - - -class Params172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot172] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection171] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions173(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params172, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot173(Slot): - pass - - -class Params173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot173] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection172] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions174(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params173, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot174(Slot): - pass - - -class Params174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot174] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection173] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions175(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params174, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot175(Slot): - pass - - -class Params175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot175] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection174] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions176(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params175, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot176(Slot): - pass - - -class Params176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot176] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection175] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions177(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params176, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot177(Slot): - pass - - -class Params177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot177] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection176] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions178(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params177, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot178(Slot): - pass - - -class Params178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot178] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection177] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions179(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params178, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot179(Slot): - pass - - -class Params179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot179] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection178] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions180(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params179, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot180(Slot): - pass - - -class Params180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot180] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection179] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions181(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params180, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions169( - RootModel[ - FormatOptions170 - | FormatOptions171 - | FormatOptions172 - | FormatOptions173 - | FormatOptions174 - | FormatOptions175 - | FormatOptions176 - | FormatOptions177 - | FormatOptions178 - | FormatOptions179 - | FormatOptions180 - | FormatOptions181 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions170 - | FormatOptions171 - | FormatOptions172 - | FormatOptions173 - | FormatOptions174 - | FormatOptions175 - | FormatOptions176 - | FormatOptions177 - | FormatOptions178 - | FormatOptions179 - | FormatOptions180 - | FormatOptions181 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot181(Slot): - pass - - -class Params181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot181] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection180] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions184(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params181, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot182(Slot): - pass - - -class Params182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot182] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection181] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions185(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params182, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot183(Slot): - pass - - -class Params183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot183] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection182] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions186(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params183, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot184(Slot): - pass - - -class Params184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot184] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection183] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions187(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params184, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot185(Slot): - pass - - -class Params185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot185] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection184] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions188(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params185, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot186(Slot): - pass - - -class Params186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot186] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection185] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions189(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params186, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot187(Slot): - pass - - -class Params187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot187] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection186] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions190(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params187, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot188(Slot): - pass - - -class Params188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot188] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection187] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions191(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params188, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot189(Slot): - pass - - -class Params189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot189] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection188] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions192(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params189, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot190(Slot): - pass - - -class Params190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot190] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection189] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions193(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params190, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot191(Slot): - pass - - -class Params191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot191] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection190] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions194(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params191, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot192(Slot): - pass - - -class Params192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot192] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection191] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions195(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params192, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions183( - RootModel[ - FormatOptions184 - | FormatOptions185 - | FormatOptions186 - | FormatOptions187 - | FormatOptions188 - | FormatOptions189 - | FormatOptions190 - | FormatOptions191 - | FormatOptions192 - | FormatOptions193 - | FormatOptions194 - | FormatOptions195 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions184 - | FormatOptions185 - | FormatOptions186 - | FormatOptions187 - | FormatOptions188 - | FormatOptions189 - | FormatOptions190 - | FormatOptions191 - | FormatOptions192 - | FormatOptions193 - | FormatOptions194 - | FormatOptions195 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions183] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown57(PriceBreakdown): - pass - - -class PricingOptions61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown57 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown58(PriceBreakdown): - pass - - -class PricingOptions62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown58 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown59(PriceBreakdown): - pass - - -class PricingOptions63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown59 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown60(PriceBreakdown): - pass - - -class PricingOptions64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown60 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown61(PriceBreakdown): - pass - - -class PricingOptions65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters27, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown61 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown62(PriceBreakdown): - pass - - -class PricingOptions66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters20, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown62 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown63(PriceBreakdown): - pass - - -class PricingOptions67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown63 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown64(PriceBreakdown): - pass - - -class PricingOptions68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters29 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown64 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown65(PriceBreakdown): - pass - - -class PricingOptions69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters30, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown65 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions60( - RootModel[ - PricingOptions61 - | PricingOptions62 - | PricingOptions63 - | PricingOptions64 - | PricingOptions65 - | PricingOptions66 - | PricingOptions67 - | PricingOptions68 - | PricingOptions69 - ] -): - root: Annotated[ - PricingOptions61 - | PricingOptions62 - | PricingOptions63 - | PricingOptions64 - | PricingOptions65 - | PricingOptions66 - | PricingOptions67 - | PricingOptions68 - | PricingOptions69, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions85(Dimensions61): - pass - - -class Dimensions87(Dimensions63): - pass - - -class Dimensions88(Dimensions64): - pass - - -class Dimensions89(Dimensions65): - pass - - -class EmbeddedProvenanceItem914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1828 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1829 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction958(Jurisdiction934): - pass - - -class Disclosure916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction958] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy920 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem914] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark914] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure916 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem914] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance913 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo118 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor70(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride117 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor70 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1830 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1831 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction959(Jurisdiction934): - pass - - -class Disclosure917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction959] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy921 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem915] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark915] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure917 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem915] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance914 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo119 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor71(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride118 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor71, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions85 | Dimensions86 | Dimensions87 | Dimensions88 | Dimensions89 | Dimensions90 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics14, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability19 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue17] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point13], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1832 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1833 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction960(Jurisdiction934): - pass - - -class Disclosure918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction960] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy922 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem916] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark916] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure918 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem916] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance915 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo120 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor72(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride119 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement7(AdCPBaseModel): - vendors: Annotated[ - list[Vendor72] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1834 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1835 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction961(Jurisdiction934): - pass - - -class Disclosure919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction961] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy923 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem917] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark917] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure919 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem917] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance916 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo121 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor73(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride120 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor73, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement9 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1836 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1837 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction962(Jurisdiction934): - pass - - -class Disclosure920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction962] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy924 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem918] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark918] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure920 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem918] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance917 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo122 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor74(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride121 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor74, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction6(AllowedAction): - pass - - -class EmbeddedProvenanceItem919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1838 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1839 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction963(Jurisdiction934): - pass - - -class Disclosure921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction963] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy925 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem919] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark919] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure921 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem919] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance918 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo123 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor75(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride122 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor75, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric7] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown6 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy7(CreativePolicy): - pass - - -class IncludedSignal6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption25] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization7(MetricOptimization): - pass - - -class EmbeddedProvenanceItem920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1840 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1841 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction964(Jurisdiction934): - pass - - -class Disclosure922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction964] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy926 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem920] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark920] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure922 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem920] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance919 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo124 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor76(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride123 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor76, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric14], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue93] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking9(ConversionTracking): - pass - - -class EmbeddedProvenanceItem921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1842 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1843 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction965(Jurisdiction934): - pass - - -class Disclosure923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction965] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy927 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem921] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark921] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure923 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem921] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance920 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image7 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1844 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1845 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction966(Jurisdiction934): - pass - - -class Disclosure924(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction966] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy928 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem922] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark922] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure924 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem922] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance921 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1846 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1847 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction967(Jurisdiction934): - pass - - -class Disclosure925(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction967] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy929 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem923] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark923] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure925 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem923] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance922 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage6 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage6] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines6 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider14(Provider): - pass - - -class TrustedMatch10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider14] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class PartialResults(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty14], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId], - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] - format_options: Annotated[ - list[FormatOptions169] | None, - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[Placement7] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions60], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast10 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement6 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement7 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms8 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard8] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy7 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction6] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities6, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy7 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal6] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption6] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules6 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization7 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization8 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness6 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking9 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch7 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard8 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed7 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment6] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch10 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot193(Slot): - pass - - -class Params193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot193] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection192] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions198(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params193, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot194(Slot): - pass - - -class Params194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot194] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection193] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions199(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params194, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot195(Slot): - pass - - -class Params195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot195] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection194] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions200(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params195, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot196(Slot): - pass - - -class Params196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot196] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection195] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions201(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params196, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot197(Slot): - pass - - -class Params197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot197] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection196] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions202(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params197, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot198(Slot): - pass - - -class Params198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot198] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection197] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions203(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params198, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot199(Slot): - pass - - -class Params199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot199] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection198] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions204(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params199, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot200(Slot): - pass - - -class Params200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot200] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection199] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions205(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params200, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot201(Slot): - pass - - -class Params201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot201] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection200] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions206(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params201, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot202(Slot): - pass - - -class Params202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot202] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection201] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions207(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params202, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot203(Slot): - pass - - -class Params203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot203] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection202] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions208(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params203, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot204(Slot): - pass - - -class Params204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot204] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection203] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions209(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params204, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions197( - RootModel[ - FormatOptions198 - | FormatOptions199 - | FormatOptions200 - | FormatOptions201 - | FormatOptions202 - | FormatOptions203 - | FormatOptions204 - | FormatOptions205 - | FormatOptions206 - | FormatOptions207 - | FormatOptions208 - | FormatOptions209 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions198 - | FormatOptions199 - | FormatOptions200 - | FormatOptions201 - | FormatOptions202 - | FormatOptions203 - | FormatOptions204 - | FormatOptions205 - | FormatOptions206 - | FormatOptions207 - | FormatOptions208 - | FormatOptions209 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot205(Slot): - pass - - -class Params205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot205] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection204] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions212(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params205, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot206(Slot): - pass - - -class Params206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot206] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection205] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions213(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params206, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot207(Slot): - pass - - -class Params207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot207] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection206] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions214(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params207, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot208(Slot): - pass - - -class Params208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot208] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection207] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions215(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params208, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot209(Slot): - pass - - -class Params209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot209] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection208] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions216(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params209, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot210(Slot): - pass - - -class Params210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot210] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection209] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions217(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params210, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot211(Slot): - pass - - -class Params211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot211] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection210] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec25] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions218(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params211, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot212(Slot): - pass - - -class Params212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot212] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection211] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions219(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params212, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot213(Slot): - pass - - -class Params213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot213] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection212] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions220(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params213, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot214(Slot): - pass - - -class Params214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot214] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection213] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat23] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource48 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource48.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions221(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params214, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot215(Slot): - pass - - -class Params215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot215] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection214] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions222(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params215, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot216(Slot): - pass - - -class Params216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot216] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection215] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions223(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params216, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions211( - RootModel[ - FormatOptions212 - | FormatOptions213 - | FormatOptions214 - | FormatOptions215 - | FormatOptions216 - | FormatOptions217 - | FormatOptions218 - | FormatOptions219 - | FormatOptions220 - | FormatOptions221 - | FormatOptions222 - | FormatOptions223 - | FormatOptions126 - ] -): - root: Annotated[ - FormatOptions212 - | FormatOptions213 - | FormatOptions214 - | FormatOptions215 - | FormatOptions216 - | FormatOptions217 - | FormatOptions218 - | FormatOptions219 - | FormatOptions220 - | FormatOptions221 - | FormatOptions222 - | FormatOptions223 - | FormatOptions126, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions211] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown66(PriceBreakdown): - pass - - -class PricingOptions71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown66 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown67(PriceBreakdown): - pass - - -class PricingOptions72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown67 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown68(PriceBreakdown): - pass - - -class PricingOptions73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown68 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown69(PriceBreakdown): - pass - - -class PricingOptions74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown69 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown70(PriceBreakdown): - pass - - -class PricingOptions75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters31, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown70 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown71(PriceBreakdown): - pass - - -class PricingOptions76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters20, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown71 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown72(PriceBreakdown): - pass - - -class PricingOptions77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown72 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown73(PriceBreakdown): - pass - - -class PricingOptions78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters33 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown73 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown74(PriceBreakdown): - pass - - -class PricingOptions79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters34, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown74 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions70( - RootModel[ - PricingOptions71 - | PricingOptions72 - | PricingOptions73 - | PricingOptions74 - | PricingOptions75 - | PricingOptions76 - | PricingOptions77 - | PricingOptions78 - | PricingOptions79 - ] -): - root: Annotated[ - PricingOptions71 - | PricingOptions72 - | PricingOptions73 - | PricingOptions74 - | PricingOptions75 - | PricingOptions76 - | PricingOptions77 - | PricingOptions78 - | PricingOptions79, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions91(Dimensions61): - pass - - -class Dimensions93(Dimensions63): - pass - - -class Dimensions94(Dimensions64): - pass - - -class Dimensions95(Dimensions65): - pass - - -class EmbeddedProvenanceItem924(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1848 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark924(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1849 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction968(Jurisdiction934): - pass - - -class Disclosure926(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction968] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy930 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem924] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark924] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure926 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem924] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance923 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo125 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor77(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride124 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor77 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem925(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1850 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark925(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1851 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction969(Jurisdiction934): - pass - - -class Disclosure927(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction969] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance924(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy931 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem925] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark925] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure927 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem925] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance924 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo126 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor78(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride125 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor78, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions91 | Dimensions92 | Dimensions93 | Dimensions94 | Dimensions95 | Dimensions96 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics15, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability20 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue18] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point14], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem926(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1852 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark926(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1853 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction970(Jurisdiction934): - pass - - -class Disclosure928(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction970] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance925(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy932 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem926] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark926] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure928 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem926] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance925 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo127 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor79(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride126 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement8(AdCPBaseModel): - vendors: Annotated[ - list[Vendor79] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem927(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1854 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark927(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1855 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction971(Jurisdiction934): - pass - - -class Disclosure929(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction971] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance926(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy933 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem927] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark927] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure929 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem927] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance926 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo128 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor80(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride127 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor80, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement10 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem928(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1856 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark928(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1857 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction972(Jurisdiction934): - pass - - -class Disclosure930(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction972] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance927(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy934 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem928] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark928] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure930 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem928] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance927 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo129 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor81(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride128 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor81, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction7(AllowedAction): - pass - - -class EmbeddedProvenanceItem929(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1858 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark929(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1859 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction973(Jurisdiction934): - pass - - -class Disclosure931(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction973] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance928(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy935 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem929] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark929] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure931 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem929] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance928 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo130 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor82(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride129 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor82, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric8] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown7 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy8(CreativePolicy): - pass - - -class IncludedSignal7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef119 | SignalRef120, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption26] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization8(MetricOptimization): - pass - - -class EmbeddedProvenanceItem930(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1860 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark930(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1861 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction974(Jurisdiction934): - pass - - -class Disclosure932(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction974] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance929(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy936 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem930] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark930] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure932 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem930] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance929 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo131 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor83(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride130 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor83, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric16], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue94] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking10(ConversionTracking): - pass - - -class EmbeddedProvenanceItem931(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1862 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark931(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1863 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction975(Jurisdiction934): - pass - - -class Disclosure933(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction975] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance930(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy937 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem931] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark931] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure933 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem931] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance930 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image8 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem932(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1864 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark932(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1865 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction976(Jurisdiction934): - pass - - -class Disclosure934(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction976] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance931(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy938 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem932] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark932] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure934 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem932] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance931 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem933(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1866 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark933(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1867 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction977(Jurisdiction934): - pass - - -class Disclosure935(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction977] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance932(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy939 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem933] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark933] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure935 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem933] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance932 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage7 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage7] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines7 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider15(Provider): - pass - - -class TrustedMatch11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider15] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class PartialResults3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty15], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] = None - format_options: Annotated[ - list[FormatOptions197], - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] - placements: Annotated[ - list[Placement8] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions70], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast11 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement7 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement8 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms9 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard9] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy8 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction7] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities7, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy8 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal7] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption7] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules7 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization8 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization9 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness7 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking10 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch8 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard9 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed8 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment7] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch11 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result957(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - partial_results: Annotated[ - list[PartialResults | PartialResults3] | None, - Field(description='Partial product results that may help inform the clarification'), - ] = None - suggestions: Annotated[ - list[str] | None, Field(description='Suggested values or options for the required input') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig50(PushNotificationConfig): - pass - - -class Dimensions97(Dimensions61): - pass - - -class Dimensions99(Dimensions63): - pass - - -class Dimensions100(Dimensions64): - pass - - -class Dimensions101(Dimensions65): - pass - - -class EmbeddedProvenanceItem934(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1868 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark934(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1869 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction978(Jurisdiction934): - pass - - -class Disclosure936(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction978] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance933(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy940 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem934] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark934] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure936 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem934] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance933 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo132 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor84(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride131 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor84 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem935(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1870 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark935(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1871 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction979(Jurisdiction934): - pass - - -class Disclosure937(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction979] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance934(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy941 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem935] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark935] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure937 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem935] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance934 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo133 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor85(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride132 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor85, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions97 - | Dimensions98 - | Dimensions99 - | Dimensions100 - | Dimensions101 - | Dimensions102 - ], - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] - metrics: Annotated[ - Metrics16, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability21 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue19] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class CoverageForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - points: Annotated[ - list[Point15], - Field( - description='Coverage or availability points. Each point reuses the standard ForecastPoint shape, MUST include a signal dimension, and MUST include metrics.coverage_rate. Use metrics.impressions for count denominators and metrics.coverage_rate for the fraction of the declared scope represented by the point.', - min_length=1, - ), - ] - forecast_range_unit: Annotated[ - Literal['availability'], - Field( - description="How to interpret the points array. Signal coverage forecasts always use 'availability' because the points describe available inventory or population coverage, not spend curves or temporal pacing." - ), - ] = 'availability' - method: ForecastMethod - scope: Annotated[ - Scope232, - Field( - description='Explicit denominator for the coverage forecast. This identifies the inventory, product, account, or custom universe that coverage_rate values are relative to. Additional seller-specific qualifiers are allowed for scopes such as line item type, ad server, inventory class, country, or flight window.' - ), - ] - bucket_semantics: Annotated[ - BucketSemantics, - Field( - description="'exclusive' means the returned signal-value buckets do not overlap with each other. 'overlapping' means one impression or user can appear in multiple returned buckets, so coverage_rate values may sum above 1.0. This field describes overlap among returned buckets; bucket_completeness declares whether the returned buckets cover the full denominator." - ), - ] - bucket_completeness: Annotated[ - BucketCompleteness, - Field( - description="'complete' means the returned buckets cover the declared denominator. For complete + exclusive forecasts, count metrics and coverage_rate values can be treated as a full partition, subject to metric additivity rules. 'partial' means omitted denominator share represents undisclosed, other, or unsupported buckets; buyers MUST NOT infer totals by summing returned points." - ), - ] - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast was computed.') - ] = None - valid_until: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast expires.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Signal5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_id: Annotated[ - SignalId | SignalId60 | SignalId | SignalId60 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - signal_ref: Annotated[ - SignalRef - | SignalRef119 - | SignalRef120 - | SignalRef - | SignalRef119 - | SignalRef120 - | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque resolved-segment handle issued by this signal source. Pass this string verbatim to activate_signal.signal_agent_segment_id, and echo it in package signal targeting when the selected product option exposes the same handle. Treat the value as provider-scoped and opaque: providers MAY namespace it so two providers can expose similarly named signals without relying on a shared taxonomy. Do not pass the signal_id object as this handle, and do not reconstruct a segment handle from categorical values when get_signals returned a resolved segment.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] - description: Annotated[ - str, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_type: Annotated[ - SignalType, - Field( - description='Commercial/provenance type of signal (marketplace, custom, owned)', - title='Signal Availability Type', - ), - ] - data_provider: Annotated[ - str | None, - Field( - description='Human-readable source name for the signal, when applicable. For data_provider-scoped signals this is the data provider name; for signal_source-scoped signals it may identify the signal source or proprietary origin.' - ), - ] = None - coverage_percentage: Annotated[ - float | None, - Field( - deprecated=True, - description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only. If coverage_forecast includes an absent bucket over the same denominator, coverage_percentage SHOULD align with 100 * (1 - absent coverage_rate.mid).', - ge=0.0, - le=100.0, - ), - ] = None - coverage_forecast: Annotated[ - CoverageForecast | None, - Field( - description='Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal.', - title='Signal Coverage Forecast', - ), - ] = None - deployments: Annotated[list[Deployments6], Field(description='Array of deployment targets')] - pricing_options: Annotated[ - list[PricingOption27] | None, - Field( - description='Pricing options available for this signal when it has an incremental price. The buyer selects one and passes its pricing_option_id in report_usage or package-level signal_targeting_groups for billing verification. Omit when pricing is unavailable to the caller, bundled into the destination product, or has no incremental cost.', - min_length=1, - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - restricted_attributes: Annotated[ - list[RestrictedAttribute] | None, - Field(description='Restricted attribute categories this signal touches.', min_length=1), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field(description='Policy categories this signal is sensitive for.', min_length=1), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' - ), - ] = None - segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None - criteria_url: AnyUrl | None = None - data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None - methodology: Methodology | None = None - audience_expansion: bool | None = None - device_expansion: bool | None = None - refresh_cadence: RefreshCadence | None = None - lookback_window: RefreshCadence | None = None - onboarder: Onboarder | None = None - countries: Annotated[list[Country] | None, Field(min_length=1)] = None - consent_basis: Annotated[ - list[ConsentBasi] | None, - Field( - description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." - ), - ] = None - modeling: Modeling | None = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - dts_compliant_version: str | None = None - - -class Result979(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError47 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig50 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - signals: Annotated[list[Signal5] | None, Field(description='Array of matching signals')] = None - errors: Annotated[ - list[Error44] | None, - Field( - description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem6] | None, - Field( - description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = None - unchanged: Annotated[ - Literal[True], - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals." - ), - ] = True - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig51(PushNotificationConfig): - pass - - -class EmbeddedProvenanceItem936(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1872 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark936(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1873 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction981(Jurisdiction934): - pass - - -class Disclosure939(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction981] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance935(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy942 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem936] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark936] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure939 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem936] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance935 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo134 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride133 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class ReportingBucket(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protocol: CloudStorageProtocol - bucket: Annotated[ - str, - Field( - description='Bucket or container name', - max_length=63, - min_length=3, - pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', - ), - ] - prefix: Annotated[ - str | None, - Field( - description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', - examples=['accounts/pinnacle/adcp', 'reporting/2024'], - max_length=512, - pattern='^[a-zA-Z0-9/_.-]+$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description='Cloud region for the bucket', - examples=['us-east-1', 'europe-west1'], - max_length=64, - pattern='^[a-z0-9-]+$', - ), - ] = None - format: Annotated[ - Format | None, - Field( - description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' - ), - ] = Format.jsonl - compression: Annotated[ - Compression | None, Field(description='Compression applied to delivered files') - ] = Compression.gzip - file_retention_days: Annotated[ - int, - Field( - description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', - examples=[14, 30, 90], - ge=1, - ), - ] - setup_instructions: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' - ), - ] = None - - -class Authentication59(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class NotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication59 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: AccountStatus - brand: Annotated[ - Brand | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: BillingParty | None = None - billing_entity: Annotated[ - BillingEntity | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: PaymentTerms | None = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: AccountScope | None = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AvailableAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: MediaBuyValidAction - mode: MediaBuyActionMode - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this buy. Absence means no commitment, not zero commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class PriceBreakdown75(PriceBreakdown): - pass - - -class Catalog4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas511(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System63 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas512(GeoPostalAreas51): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas513(GeoPostalAreas52): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas514(GeoPostalAreas53): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas515(GeoPostalAreas54): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas516(GeoPostalAreas55): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas517(GeoPostalAreas56): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas518(GeoPostalAreas57): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas519(GeoPostalAreas58): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas520(GeoPostalAreas59): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas521(GeoPostalAreas510): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas512 - | GeoPostalAreas513 - | GeoPostalAreas514 - | GeoPostalAreas515 - | GeoPostalAreas516 - | GeoPostalAreas517 - | GeoPostalAreas518 - | GeoPostalAreas519 - | GeoPostalAreas520 - | GeoPostalAreas521 - ] -): - root: Annotated[ - GeoPostalAreas512 - | GeoPostalAreas513 - | GeoPostalAreas514 - | GeoPostalAreas515 - | GeoPostalAreas516 - | GeoPostalAreas517 - | GeoPostalAreas518 - | GeoPostalAreas519 - | GeoPostalAreas520 - | GeoPostalAreas521, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude2511(GeoPostalAreas511): - pass - - -class GeoPostalAreasExclude2512(GeoPostalAreasExclude251): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2513(GeoPostalAreasExclude252): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2514(GeoPostalAreasExclude253): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2515(GeoPostalAreasExclude254): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2516(GeoPostalAreasExclude255): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2517(GeoPostalAreasExclude256): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2518(GeoPostalAreasExclude257): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2519(GeoPostalAreasExclude258): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2520(GeoPostalAreasExclude259): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2521(GeoPostalAreasExclude2510): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude2512 - | GeoPostalAreasExclude2513 - | GeoPostalAreasExclude2514 - | GeoPostalAreasExclude2515 - | GeoPostalAreasExclude2516 - | GeoPostalAreasExclude2517 - | GeoPostalAreasExclude2518 - | GeoPostalAreasExclude2519 - | GeoPostalAreasExclude2520 - | GeoPostalAreasExclude2521 - ] -): - root: Annotated[ - GeoPostalAreasExclude2512 - | GeoPostalAreasExclude2513 - | GeoPostalAreasExclude2514 - | GeoPostalAreasExclude2515 - | GeoPostalAreasExclude2516 - | GeoPostalAreasExclude2517 - | GeoPostalAreasExclude2518 - | GeoPostalAreasExclude2519 - | GeoPostalAreasExclude2520 - | GeoPostalAreasExclude2521, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude26(GeoPostalAreas6): - pass - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window14 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AgeVerificationMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: TravelTimeUnit - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: DistanceUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas6] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude26] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting12] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem937(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1874 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark937(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1875 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction982(Jurisdiction934): - pass - - -class Disclosure940(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction982] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance936(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy943 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem937] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark937] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure940 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem937] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance936 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo135 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor86(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride134 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor86, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement11 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem938(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1876 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark938(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1877 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction983(Jurisdiction934): - pass - - -class Disclosure941(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction983] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance937(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy944 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem938] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark938] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure941 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem938] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance937 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo136 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor87(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride135 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor87, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class CommittedMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' - ), - ] - - -class EmbeddedProvenanceItem939(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1878 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark939(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1879 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction984(Jurisdiction934): - pass - - -class Disclosure942(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction984] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance938(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy945 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem939] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark939] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure942 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem939] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance938 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo137 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor88(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride136 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor88, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this vendor metric became part of the contract.' - ), - ] - - -class CommittedMetrics9(RootModel[CommittedMetrics | CommittedMetrics11]): - root: Annotated[ - CommittedMetrics | CommittedMetrics11, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class OptimizationGoals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - SupportedMetric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target91 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: EventType - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When present without a value-oriented target, the seller may use it for delivery reporting (conversion_value, roas) but must not change the optimization objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - float | None, - Field( - description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class AttributionWindow10(AdCPBaseModel): - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target92 | Target93 | Target94 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow10 | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem940(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1880 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark940(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1881 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction985(Jurisdiction934): - pass - - -class Disclosure943(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction985] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance939(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy946 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem940] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark940] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure943 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem940] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance939 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo138 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor89(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride137 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor89, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target95 | Target96 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals8(RootModel[OptimizationGoals | OptimizationGoals10 | OptimizationGoals11]): - root: Annotated[ - OptimizationGoals | OptimizationGoals10 | OptimizationGoals11, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') - ] - canceled_by: CanceledBy - reason: Annotated[ - str | None, Field(description='Reason the package was canceled.', max_length=500) - ] = None - acknowledged_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller acknowledged the cancellation. Confirms inventory has been released and billing stopped. Absent until the seller processes the cancellation.' - ), - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] - product_id: Annotated[ - str | None, - Field( - description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget allocation for this package in the currency specified by the pricing option', - ge=0.0, - ), - ] = None - pacing: Pacing | None = None - pricing_option_id: Annotated[ - str | None, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] = None - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown75 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - catalogs: Annotated[ - list[Catalog4] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs6] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms10 | None, - Field( - description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard10] | None, - Field( - description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics9] | None, - Field( - description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. The unified shape is symmetric with `missing_metrics` and `aggregated_totals.metric_aggregates` — same atomic unit `(scope, metric_id, qualifier)` across contract, diff, and delivery, so reconciliation collapses to a row-level join on the tuple. Replaces the parallel-array design that shipped briefly in #3510.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'impressions', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'spend', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'completed_views', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'committed_at': '2026-05-30T14:22:00Z', - }, - ] - ], - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment] | None, - Field(description='Creative assets assigned to this package'), - ] = None - format_ids_to_provide: Annotated[ - list[FormatIdsToProvideItem] | None, - Field(description='Format IDs that creative assets will be provided for this package'), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals8] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - canceled: Annotated[ - bool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' - ), - ] = False - cancellation: Annotated[ - Cancellation | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", - max_length=100, - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FrequencyCap5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress4 | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window16 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class PlannedDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo: Annotated[Geo | None, Field(description='Geographic targeting the seller will apply.')] = ( - None - ) - channels: Annotated[ - list[MediaChannel] | None, Field(description='Channels the seller will deliver on.') - ] = None - start_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight start the seller will use.') - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight end the seller will use.') - ] = None - frequency_cap: Annotated[ - FrequencyCap5 | None, - Field(description='Frequency cap the seller will apply.', title='Frequency Cap'), - ] = None - audience_summary: Annotated[ - str | None, - Field(description='Human-readable summary of the audience the seller will target.'), - ] = None - audience_targeting: Annotated[ - list[AudienceTargeting | AudienceTargeting7 | AudienceTargeting8 | AudienceTargeting9] - | None, - Field( - description='Structured audience targeting the seller will activate. Each entry is either a signal reference or a descriptive criterion. When present, governance agents MUST use this for bias/fairness validation and SHOULD ignore audience_summary for validation purposes. The audience_summary field is a human-readable rendering of this array, not an independent declaration.', - min_length=1, - ), - ] = None - total_budget: Annotated[ - float | None, Field(description='Total budget the seller will deliver against.', ge=0.0) - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for the budget.', pattern='^[A-Z]{3}$'), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field(description='Registry policy IDs the seller will enforce for this delivery.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError48 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig51 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - media_buy_id: Annotated[ - str, Field(description="Seller's unique identifier for the created media buy") - ] - account: Annotated[ - Account | None, - Field( - description='Account billed for this media buy. Includes advertiser, billing proxy (if any), and rate card applied.', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient | None, - Field( - description='Per-buy invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - media_buy_status: MediaBuyStatus | None = None - confirmed_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when this media buy was committed by the seller. Stable after it is set; do not update on later pause/resume/status/reporting transitions. May be null in deferred or manual-approval flows until seller commitment occurs.' - ), - ] - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') - ] = None - revision: Annotated[ - int, - Field( - description='Initial revision number for this media buy. Use in subsequent update_media_buy requests intended to change state for optimistic concurrency.', - ge=1, - ), - ] - currency: Annotated[ - str | None, - Field( - description="ISO 4217 currency code (e.g., USD, EUR, GBP) for monetary values at this media buy level. total_budget is denominated in this currency. Package-level fields may override with package.currency. In proposal mode the seller derives this from the request's total_budget object (total_budget.currency); in manual mode it is present when all packages share a currency. Matches the currency field in subsequent get_media_buys responses.", - pattern='^[A-Z]{3}$', - ), - ] = None - total_budget: Annotated[ - float | None, - Field( - description='Total budget amount across all packages, denominated in currency. Note: the create_media_buy request encodes total_budget as an object {amount, currency} (proposal mode only); this response field is the flattened scalar amount, with currency promoted to the sibling currency field. Present when the seller can compute a deterministic aggregate — always in proposal mode, conditionally in manual mode when all packages share a currency. Matches the total_budget field in subsequent get_media_buys responses.', - ge=0.0, - ), - ] = None - valid_actions: Annotated[ - list[MediaBuyValidAction] | None, - Field( - description='Flat-vocabulary actions the buyer can perform on this media buy after creation. Saves a round-trip to get_media_buys. Deprecated in favor of `available_actions[]`, which carries `mode`, optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.' - ), - ] = None - available_actions: Annotated[ - list[AvailableAction] | None, - Field( - description='Structured per-buy resolution of actions available immediately after creation. Authoritative — see `get-media-buys-response.json` for full semantics.' - ), - ] = None - packages: Annotated[ - list[Package], - Field(description='Array of created packages with complete state information'), - ] - planned_delivery: Annotated[ - PlannedDelivery | None, - Field( - description="The seller's interpreted delivery parameters. Describes what the seller will actually run -- geo, channels, flight dates, frequency caps, and budget. Present when the account has governance_agents or when the seller chooses to provide delivery transparency.", - title='Planned Delivery', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Authentication60(Authentication): - pass - - -class PushNotificationConfig52(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication60 | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Result990(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError49 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig52 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error46], - Field(description='Array of errors explaining why the operation failed', min_length=1), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig53(PushNotificationConfig52): - pass - - -class Result991(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError50 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig53 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error47] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig54(PushNotificationConfig52): - pass - - -class PriceBreakdown76(PriceBreakdown): - pass - - -class Catalog5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping37] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class GeoPostalAreas711(GeoPostalAreas511): - pass - - -class GeoPostalAreas712(GeoPostalAreas71): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas713(GeoPostalAreas72): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas714(GeoPostalAreas73): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas715(GeoPostalAreas74): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas716(GeoPostalAreas75): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas717(GeoPostalAreas76): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas718(GeoPostalAreas77): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas719(GeoPostalAreas78): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas720(GeoPostalAreas79): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas721(GeoPostalAreas710): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas7( - RootModel[ - GeoPostalAreas712 - | GeoPostalAreas713 - | GeoPostalAreas714 - | GeoPostalAreas715 - | GeoPostalAreas716 - | GeoPostalAreas717 - | GeoPostalAreas718 - | GeoPostalAreas719 - | GeoPostalAreas720 - | GeoPostalAreas721 - ] -): - root: Annotated[ - GeoPostalAreas712 - | GeoPostalAreas713 - | GeoPostalAreas714 - | GeoPostalAreas715 - | GeoPostalAreas716 - | GeoPostalAreas717 - | GeoPostalAreas718 - | GeoPostalAreas719 - | GeoPostalAreas720 - | GeoPostalAreas721, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas8(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude2711(GeoPostalAreas511): - pass - - -class GeoPostalAreasExclude2712(GeoPostalAreasExclude271): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2713(GeoPostalAreasExclude272): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2714(GeoPostalAreasExclude273): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2715(GeoPostalAreasExclude274): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2716(GeoPostalAreasExclude275): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2717(GeoPostalAreasExclude276): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2718(GeoPostalAreasExclude277): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2719(GeoPostalAreasExclude278): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2720(GeoPostalAreasExclude279): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2721(GeoPostalAreasExclude2710): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude27( - RootModel[ - GeoPostalAreasExclude2712 - | GeoPostalAreasExclude2713 - | GeoPostalAreasExclude2714 - | GeoPostalAreasExclude2715 - | GeoPostalAreasExclude2716 - | GeoPostalAreasExclude2717 - | GeoPostalAreasExclude2718 - | GeoPostalAreasExclude2719 - | GeoPostalAreasExclude2720 - | GeoPostalAreasExclude2721 - ] -): - root: Annotated[ - GeoPostalAreasExclude2712 - | GeoPostalAreasExclude2713 - | GeoPostalAreasExclude2714 - | GeoPostalAreasExclude2715 - | GeoPostalAreasExclude2716 - | GeoPostalAreasExclude2717 - | GeoPostalAreasExclude2718 - | GeoPostalAreasExclude2719 - | GeoPostalAreasExclude2720 - | GeoPostalAreasExclude2721, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude28(GeoPostalAreas6): - pass - - -class FrequencyCap6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress5 | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window17 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class GeoProximityItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry6 | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TargetingOverlay3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - list[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - list[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - list[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas7 | GeoPostalAreas8] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - list[GeoPostalAreasExclude27 | GeoPostalAreasExclude28] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups3 | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting16] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap6 | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem5] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem941(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1882 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark941(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1883 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction986(Jurisdiction934): - pass - - -class Disclosure944(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction986] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance940(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy947 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem941] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark941] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure944 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem941] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance940 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo139 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor90(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride138 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor90, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement12 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem942(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1884 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark942(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1885 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction987(Jurisdiction934): - pass - - -class Disclosure945(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction987] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance941(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy948 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem942] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark942] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure945 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem942] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance941 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo140 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor91(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride139 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor91, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow11 | None, - Field( - description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class CommittedMetrics13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier8 | None, - Field( - description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' - ), - ] - - -class EmbeddedProvenanceItem943(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1886 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark943(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1887 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction988(Jurisdiction934): - pass - - -class Disclosure946(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction988] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance942(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy949 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem943] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark943] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure946 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem943] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance942 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo141 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor92(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride140 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor92, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this vendor metric became part of the contract.' - ), - ] - - -class CommittedMetrics12(RootModel[CommittedMetrics13 | CommittedMetrics14]): - root: Annotated[ - CommittedMetrics13 | CommittedMetrics14, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class OptimizationGoals13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - SupportedMetric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency4 | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target91 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class AttributionWindow12(AdCPBaseModel): - post_click: Annotated[ - PostClick4 | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView4 | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target92 | Target93 | Target94 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow12 | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem944(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1888 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark944(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1889 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction989(Jurisdiction934): - pass - - -class Disclosure947(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction989] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance943(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy950 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem944] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark944] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure947 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem944] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance943 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo142 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor93(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride141 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor93, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target95 | Target96 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals12( - RootModel[OptimizationGoals13 | OptimizationGoals14 | OptimizationGoals15] -): - root: Annotated[ - OptimizationGoals13 | OptimizationGoals14 | OptimizationGoals15, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class AffectedPackage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] - product_id: Annotated[ - str | None, - Field( - description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget allocation for this package in the currency specified by the pricing option', - ge=0.0, - ), - ] = None - pacing: Pacing | None = None - pricing_option_id: Annotated[ - str | None, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] = None - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown76 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - catalogs: Annotated[ - list[Catalog5] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs9] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay3 | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms11 | None, - Field( - description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard11] | None, - Field( - description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics12] | None, - Field( - description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. The unified shape is symmetric with `missing_metrics` and `aggregated_totals.metric_aggregates` — same atomic unit `(scope, metric_id, qualifier)` across contract, diff, and delivery, so reconciliation collapses to a row-level join on the tuple. Replaces the parallel-array design that shipped briefly in #3510.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'impressions', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'spend', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'completed_views', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'committed_at': '2026-05-30T14:22:00Z', - }, - ] - ], - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment4] | None, - Field(description='Creative assets assigned to this package'), - ] = None - format_ids_to_provide: Annotated[ - list[FormatIdsToProvideItem] | None, - Field(description='Format IDs that creative assets will be provided for this package'), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals12] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - canceled: Annotated[ - bool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' - ), - ] = False - cancellation: Annotated[ - Cancellation | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", - max_length=100, - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AvailableAction3(AvailableAction): - pass - - -class Result995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError51 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig54 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - media_buy_id: Annotated[str, Field(description="Seller's identifier for the media buy")] - media_buy_status: MediaBuyStatus | None = None - revision: Annotated[ - int, - Field( - description='Revision number after this update. Use this value in subsequent update_media_buy requests intended to change state for optimistic concurrency. Exact idempotency replays return the prior revision and do not increment revision.', - ge=1, - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for monetary values at this media buy level. Echoed when the update affects budget or currency. Matches the currency field in subsequent get_media_buys responses.', - pattern='^[A-Z]{3}$', - ), - ] = None - total_budget: Annotated[ - float | None, - Field( - description='Updated total budget amount across all packages, denominated in currency. Echoed when the update affects package budgets so buyers can verify the new aggregate without a round-trip to get_media_buys. Matches the total_budget field in subsequent get_media_buys responses.', - ge=0.0, - ), - ] = None - implementation_date: Annotated[ - AwareDatetime | None, - Field(description='ISO 8601 timestamp when changes take effect (null if pending approval)'), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient3 | None, - Field( - description='Updated invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - affected_packages: Annotated[ - list[AffectedPackage] | None, - Field( - description='For package-level updates, array of full Package objects showing complete post-update state for each directly modified package. This is a state snapshot, not a sparse delta: sellers MUST NOT return package_id-only stubs. Campaign-level updates that do not modify packages may return an empty array.' - ), - ] = None - valid_actions: Annotated[ - list[MediaBuyValidAction] | None, - Field( - description='Flat-vocabulary actions the buyer can perform after this update. Saves a round-trip to get_media_buys. Deprecated in favor of `available_actions[]`, which carries `mode`, optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.' - ), - ] = None - available_actions: Annotated[ - list[AvailableAction3] | None, - Field( - description='Structured per-buy resolution of actions available after this update. Authoritative — see `get-media-buys-response.json` for full semantics.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig55(PushNotificationConfig52): - pass - - -class Result1000(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError52 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig55 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error50], - Field(description='Array of errors explaining why the operation failed', min_length=1), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig56(PushNotificationConfig52): - pass - - -class Result1001(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError53 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig56 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error51] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AttributionWindow13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - PostClick5 | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView5 | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class ByEventTypeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: EventType - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[float, Field(description='Number of events of this type', ge=0.0)] - value: Annotated[ - float | None, Field(description='Total monetary value of events of this type', ge=0.0) - ] = None - - -class EmbeddedProvenanceItem945(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1890 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark945(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1891 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction990(Jurisdiction934): - pass - - -class Disclosure948(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction990] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance944(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy951 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem945] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark945] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure948 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem945] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance944 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo143 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor94(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride142 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor94 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class ByActionSourceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_source: ActionSource - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[ - float, Field(description='Number of conversions from this action source', ge=0.0) - ] - value: Annotated[ - float | None, - Field(description='Total monetary value of conversions from this action source', ge=0.0), - ] = None - - -class EmbeddedProvenanceItem946(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1892 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark946(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1893 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction991(Jurisdiction934): - pass - - -class Disclosure949(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction991] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance945(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy952 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem946] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark946] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure949 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem946] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance945 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo144 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor95(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride143 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor95, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Totals(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability22 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue20] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - effective_rate: Annotated[float | None, Field(ge=0.0)] = None - - -class EmbeddedProvenanceItem947(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1894 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark947(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1895 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction992(Jurisdiction934): - pass - - -class Disclosure950(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction992] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance946(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy953 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem947] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark947] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure950 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem947] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance946 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo145 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor96(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride144 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor96 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem948(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1896 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark948(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1897 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction993(Jurisdiction934): - pass - - -class Disclosure951(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction993] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance947(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy954 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem948] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark948] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure951 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem948] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance947 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo146 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor97(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride145 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue21(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor97, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByPackageItem(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow7 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics6 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability23 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue21] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - package_id: str | None = None - pacing_index: Annotated[float | None, Field(ge=0.0)] = None - pricing_model: PricingModel | None = None - rate: Annotated[float | None, Field(ge=0.0)] = None - currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None - delivery_status: DeliveryStatus | None = None - paused: bool | None = None - is_final: bool | None = None - finalized_at: AwareDatetime | None = None - measurement_window: Annotated[str | None, Field(max_length=50)] = None - supersedes_window: Annotated[str | None, Field(max_length=50)] = None - - -class MediaBuyDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier.")] - status: Annotated[ - Status314, - Field( - description="Current media buy lifecycle or reporting status. This is distinct from the webhook envelope's top-level task status." - ), - ] - expected_availability: Annotated[ - AwareDatetime | None, - Field( - description='When delayed data is expected to be available. Present when status is reporting_delayed.' - ), - ] = None - is_adjusted: Annotated[ - bool | None, - Field( - description='Indicates this row contains updated data for a previously reported period.' - ), - ] = None - is_final: Annotated[ - bool | None, - Field(description="Whether this row's delivery data is final for the reporting period."), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description='Timestamp when this row became final. Present only when is_final is true.' - ), - ] = None - pricing_model: PricingModel | None = None - totals: Totals - by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package.')] - - -class Result1005(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notification_type: Annotated[ - NotificationType3, - Field( - description='Type of delivery-report notification: scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = corrected data for the same window, window_update = a wider measurement window supersedes a prior window.' - ), - ] - partial_data: Annotated[ - bool | None, - Field( - description='Indicates if any media buys in this webhook have missing or delayed data.' - ), - ] = None - unavailable_count: Annotated[ - int | None, - Field( - description='Number of media buys with reporting_delayed or failed status when partial_data is true.', - ge=0, - ), - ] = None - sequence_number: Annotated[ - int | None, - Field( - description='Sequential notification number for this reporting webhook stream.', ge=1 - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp for the next expected notification. Omitted on final notifications.' - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, Field(description='UTC date range covered by the delivery report.') - ] - currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] - attribution_window: Annotated[ - AttributionWindow13 | None, - Field( - description='Attribution methodology and lookback windows used for conversion metrics in this report.', - title='Attribution Window', - ), - ] = None - media_buy_deliveries: Annotated[ - list[MediaBuyDelivery], - Field( - description='Delivery rows for one or more media buys included in this notification.' - ), - ] - errors: Annotated[ - list[Error53] | None, Field(description='Task-specific delivery errors or warnings.') - ] = None - sandbox: bool | None = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig57(PushNotificationConfig52): - pass - - -class EmbeddedProvenanceItem949(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1898 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark949(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1899 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction994(Jurisdiction934): - pass - - -class Disclosure952(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction994] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance948(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy955 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem949] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark949] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure952 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem949] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance948 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem950(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1900 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark950(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1901 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction995(Jurisdiction934): - pass - - -class Disclosure953(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction995] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance949(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy956 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem950] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark950] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure953 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem950] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets477(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance949 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem951(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1902 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark951(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1903 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction996(Jurisdiction934): - pass - - -class Disclosure954(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction996] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance950(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy957 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem951] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark951] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure954 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem951] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets478(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance950 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem952(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1904 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark952(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1905 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction997(Jurisdiction934): - pass - - -class Disclosure955(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction997] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance951(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy958 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem952] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark952] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure955 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem952] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets479(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance951 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem953(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1906 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark953(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1907 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction998(Jurisdiction934): - pass - - -class Disclosure956(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction998] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance952(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy959 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem953] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark953] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure956 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem953] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets480(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance952 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem954(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1908 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark954(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1909 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction999(Jurisdiction934): - pass - - -class Disclosure957(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction999] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance953(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy960 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem954] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark954] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure957 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem954] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets481(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance953 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem955(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1910 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark955(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1911 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1000(Jurisdiction934): - pass - - -class Disclosure958(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1000] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance954(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy961 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem955] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark955] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure958 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem955] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets482(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance954 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem956(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1912 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark956(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1913 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1001(Jurisdiction934): - pass - - -class Disclosure959(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1001] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance955(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy962 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem956] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark956] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure959 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem956] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets483(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance955 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem957(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1914 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark957(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1915 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1002(Jurisdiction934): - pass - - -class Disclosure960(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1002] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance956(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy963 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem957] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark957] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure960 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem957] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance956 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem958(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1916 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark958(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1917 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1003(Jurisdiction934): - pass - - -class Disclosure961(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1003] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance957(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy964 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem958] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark958] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure961 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem958] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets485(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance957 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem959(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1918 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark959(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1919 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1004(Jurisdiction934): - pass - - -class Disclosure962(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1004] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance958(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy965 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem959] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark959] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure962 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem959] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets486(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance958 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem960(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1920 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark960(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1921 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1005(Jurisdiction934): - pass - - -class Disclosure963(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1005] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance959(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy966 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem960] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark960] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure963 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem960] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets487(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance959 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem961(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1922 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark961(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1923 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1006(Jurisdiction934): - pass - - -class Disclosure964(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1006] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance960(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy967 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem961] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark961] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure964 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem961] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets488(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance960 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem962(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1924 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark962(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1925 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1007(Jurisdiction934): - pass - - -class Disclosure965(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1007] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance961(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy968 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem962] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark962] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure965 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem962] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets489(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance961 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets490(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction1008] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets491(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets492(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping38] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem963(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1926 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark963(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1927 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1009(Jurisdiction934): - pass - - -class Disclosure966(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1009] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance962(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy969 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem963] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark963] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure966 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem963] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets493(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance962 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem964(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1928 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark964(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1929 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1010(Jurisdiction934): - pass - - -class Disclosure967(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1010] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance963(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy970 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem964] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark964] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure967 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem964] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance963 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem965(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1930 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark965(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1931 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1011(Jurisdiction934): - pass - - -class Disclosure968(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1011] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance964(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy971 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem965] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark965] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure968 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem965] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance964 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem966(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1932 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark966(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1933 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1012(Jurisdiction934): - pass - - -class Disclosure969(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1012] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance965(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy972 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem966] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark966] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure969 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem966] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance965 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem967(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1934 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark967(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1935 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1013(Jurisdiction934): - pass - - -class Disclosure970(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1013] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance966(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy973 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem967] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark967] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure970 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem967] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets494(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media69, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance966 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem968(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1936 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark968(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1937 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1014(Jurisdiction934): - pass - - -class Disclosure971(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1014] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance967(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy974 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem968] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark968] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure971 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem968] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets495(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance967 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem969(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1938 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark969(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1939 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1015(Jurisdiction934): - pass - - -class Disclosure972(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1015] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance968(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy975 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem969] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark969] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure972 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem969] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets496(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance968 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem970(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1940 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark970(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1941 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1016(Jurisdiction934): - pass - - -class Disclosure973(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1016] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance969(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy976 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem970] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark970] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure973 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem970] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets497(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance969 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem971(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1942 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark971(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1943 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1017(Jurisdiction934): - pass - - -class Disclosure974(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1017] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance970(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy977 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem971] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark971] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure974 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem971] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance970 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem972(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1944 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark972(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1945 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1018(Jurisdiction934): - pass - - -class Disclosure975(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1018] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance971(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy978 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem972] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark972] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure975 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem972] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance971 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem973(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1946 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark973(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1947 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1019(Jurisdiction934): - pass - - -class Disclosure976(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1019] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance972(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy979 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem973] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark973] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure976 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem973] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance972 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem974(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1948 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark974(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1949 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1020(Jurisdiction934): - pass - - -class Disclosure977(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1020] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance973(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy980 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem974] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark974] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure977 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem974] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4985(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance973 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem975(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1950 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark975(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1951 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1021(Jurisdiction934): - pass - - -class Disclosure978(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1021] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance974(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy981 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem975] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark975] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure978 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem975] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4986(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance974 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem976(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1952 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark976(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1953 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1022(Jurisdiction934): - pass - - -class Disclosure979(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1022] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance975(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy982 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem976] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark976] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure979 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem976] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4987(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance975 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem977(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1954 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark977(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1955 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1023(Jurisdiction934): - pass - - -class Disclosure980(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1023] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance976(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy983 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem977] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark977] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure980 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem977] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4988(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance976 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem978(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1956 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark978(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1957 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1024(Jurisdiction934): - pass - - -class Disclosure981(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1024] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance977(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy984 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem978] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark978] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure981 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem978] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4989(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance977 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem979(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1958 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark979(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1959 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1025(Jurisdiction934): - pass - - -class Disclosure982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1025] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance978(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy985 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem979] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark979] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure982 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem979] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance978 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem980(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1960 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark980(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1961 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1026(Jurisdiction934): - pass - - -class Disclosure983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1026] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance979(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy986 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem980] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark980] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure983 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem980] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance979 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem981(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1962 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark981(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1963 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1027(Jurisdiction934): - pass - - -class Disclosure984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1027] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance980(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy987 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem981] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark981] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure984 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem981] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance980 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1964 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1965 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1028(Jurisdiction934): - pass - - -class Disclosure985(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1028] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance981(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy988 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem982] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark982] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure985 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem982] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance981 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1966 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1967 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1029(Jurisdiction934): - pass - - -class Disclosure986(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1029] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance982(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy989 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem983] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark983] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure986 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem983] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance982 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1968 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1969 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1030(Jurisdiction934): - pass - - -class Disclosure987(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1030] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance983(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy990 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem984] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark984] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure987 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem984] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance983 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure36(RequiredDisclosure): - pass - - -class Compliance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure36] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets49817(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset36] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance36 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets49818(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping39] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem985(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1970 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark985(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1971 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1032(Jurisdiction934): - pass - - -class Disclosure988(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1032] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance984(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy991 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem985] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark985] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure988 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem985] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization36 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance984 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem986(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1972 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark986(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1973 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1033(Jurisdiction934): - pass - - -class Disclosure989(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1033] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance985(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy992 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem986] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark986] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure989 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem986] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance985 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem987(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1974 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark987(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1975 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1034(Jurisdiction934): - pass - - -class Disclosure990(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1034] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance986(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy993 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem987] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark987] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure990 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem987] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance986 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem988(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1976 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark988(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1977 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1035(Jurisdiction934): - pass - - -class Disclosure991(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1035] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance987(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy994 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem988] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark988] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure991 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem988] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance987 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem989(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1978 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark989(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1979 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1036(Jurisdiction934): - pass - - -class Disclosure992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1036] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance988(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy995 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem989] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark989] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure992 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem989] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media70 | Media71, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl35 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance988 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem990(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1980 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark990(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1981 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1037(Jurisdiction934): - pass - - -class Disclosure993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1037] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance989(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy996 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem990] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark990] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure993 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem990] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance989 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem991(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1982 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark991(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1983 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1038(Jurisdiction934): - pass - - -class Disclosure994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1038] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance990(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy997 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem991] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark991] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure994 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem991] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance990 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1984 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1985 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1039(Jurisdiction934): - pass - - -class Disclosure995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1039] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance991(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy998 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem992] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark992] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure995 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem992] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance991 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets4981( - RootModel[ - Assets4982 - | Assets4983 - | Assets4984 - | Assets4985 - | Assets4986 - | Assets4987 - | Assets4988 - | Assets4989 - | Assets49810 - | Assets49811 - | Assets49812 - | Assets49813 - | Assets49814 - | Assets49815 - | Assets490 - | Assets49817 - | Assets49818 - | Assets49819 - | Assets49820 - | Assets49821 - | Assets49822 - | Assets49823 - ] -): - root: Annotated[ - Assets4982 - | Assets4983 - | Assets4984 - | Assets4985 - | Assets4986 - | Assets4987 - | Assets4988 - | Assets4989 - | Assets49810 - | Assets49811 - | Assets49812 - | Assets49813 - | Assets49814 - | Assets49815 - | Assets490 - | Assets49817 - | Assets49818 - | Assets49819 - | Assets49820 - | Assets49821 - | Assets49822 - | Assets49823, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets498(RootModel[list[Assets4981]]): - root: Annotated[list[Assets4981], Field(min_length=1)] - - -class EmbeddedProvenanceItem993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1986 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1987 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1040(Jurisdiction934): - pass - - -class Disclosure996(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1040] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy999 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem993] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark993] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure996 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem993] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance992 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo147 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand50(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride146 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[RightUse], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country94] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: RightType | None = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: CreativeIdentifierType - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class EmbeddedProvenanceItem994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1988 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1989 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1041(Jurisdiction934): - pass - - -class Disclosure997(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1041] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1000 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem994] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark994] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure997 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem994] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets477 - | Assets478 - | Assets479 - | Assets480 - | Assets481 - | Assets482 - | Assets483 - | Assets484 - | Assets485 - | Assets486 - | Assets487 - | Assets488 - | Assets489 - | Assets490 - | Assets491 - | Assets492 - | Assets493 - | Assets494 - | Assets495 - | Assets496 - | Assets497 - | Assets498, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand50 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right20] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance993 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1990 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1991 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1042(Jurisdiction934): - pass - - -class Disclosure998(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1042] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1001 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem995] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark995] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure998 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem995] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets499(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance994 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem996(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1992 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark996(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1993 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1043(Jurisdiction934): - pass - - -class Disclosure999(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1043] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1002 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem996] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark996] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure999 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem996] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets500(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance995 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem997(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1994 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark997(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1995 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1044(Jurisdiction934): - pass - - -class Disclosure1000(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1044] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance996(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1003 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem997] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark997] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1000 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem997] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets501(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance996 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem998(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1996 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark998(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1997 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1045(Jurisdiction934): - pass - - -class Disclosure1001(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1045] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance997(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1004 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem998] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark998] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1001 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem998] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets502(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance997 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem999(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1998 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark999(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1999 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1046(Jurisdiction934): - pass - - -class Disclosure1002(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1046] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance998(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1005 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem999] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark999] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1002 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem999] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets503(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance998 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1000(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2000 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1000(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2001 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1047(Jurisdiction934): - pass - - -class Disclosure1003(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1047] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance999(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1006 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1000] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1000] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1003 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1000] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets504(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance999 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1001(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2002 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1001(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2003 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1048(Jurisdiction934): - pass - - -class Disclosure1004(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1048] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1000(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1007 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1001] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1001] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1004 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1001] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets505(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1000 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1002(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2004 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1002(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2005 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1049(Jurisdiction934): - pass - - -class Disclosure1005(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1049] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1001(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1008 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1002] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1002] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1005 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1002] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets506(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1001 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1003(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2006 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1003(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2007 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1050(Jurisdiction934): - pass - - -class Disclosure1006(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1050] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1002(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1009 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1003] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1003] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1006 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1003] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets507(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1002 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1004(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2008 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1004(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2009 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1051(Jurisdiction934): - pass - - -class Disclosure1007(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1051] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1003(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1010 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1004] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1004] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1007 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1004] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets508(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1003 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1005(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2010 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1005(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2011 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1052(Jurisdiction934): - pass - - -class Disclosure1008(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1052] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1004(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1011 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1005] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1005] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1008 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1005] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets509(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1004 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1006(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2012 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1006(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2013 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1053(Jurisdiction934): - pass - - -class Disclosure1009(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1053] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1005(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1012 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1006] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1006] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1009 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1006] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1005 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1007(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2014 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1007(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2015 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1054(Jurisdiction934): - pass - - -class Disclosure1010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1054] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1006(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1013 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1007] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1007] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1010 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1007] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1006 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1008(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2016 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1008(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2017 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1055(Jurisdiction934): - pass - - -class Disclosure1011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1055] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1007(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1014 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1008] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1008] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1011 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1008] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1007 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets513(Assets490): - pass - - -class RequiredDisclosure37(RequiredDisclosure): - pass - - -class Compliance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure37] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets514(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset37] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance37 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets515(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping40] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1009(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2018 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1009(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2019 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1057(Jurisdiction934): - pass - - -class Disclosure1012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1057] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1008(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1015 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1009] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1009] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1012 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1009] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization37 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1008 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2020 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2021 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1058(Jurisdiction934): - pass - - -class Disclosure1013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1058] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1009(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1016 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1010] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1010] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1013 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1010] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1009 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2022 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2023 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1059(Jurisdiction934): - pass - - -class Disclosure1014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1059] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1017 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1011] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1011] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1014 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1011] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1010 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2024 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2025 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1060(Jurisdiction934): - pass - - -class Disclosure1015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1060] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1018 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1012] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1012] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1015 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1012] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1011 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2026 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2027 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1061(Jurisdiction934): - pass - - -class Disclosure1016(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1061] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1019 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1013] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1013] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1016 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1013] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media72 | Media73, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl36 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1012 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2028 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2029 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1062(Jurisdiction934): - pass - - -class Disclosure1017(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1062] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1020 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1014] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1014] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1017 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1014] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1013 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2030 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2031 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1063(Jurisdiction934): - pass - - -class Disclosure1018(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1063] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1021 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1015] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1015] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1018 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1015] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1014 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1016(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2032 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1016(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2033 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1064(Jurisdiction934): - pass - - -class Disclosure1019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1064] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1022 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1016] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1016] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1019 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1016] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1015 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1017(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2034 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1017(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2035 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1065(Jurisdiction934): - pass - - -class Disclosure1020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1065] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1016(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1023 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1017] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1017] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1020 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1017] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1016 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1018(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2036 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1018(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2037 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1066(Jurisdiction934): - pass - - -class Disclosure1021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1066] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1017(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1024 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1018] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1018] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1021 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1018] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1017 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2038 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2039 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1067(Jurisdiction934): - pass - - -class Disclosure1022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1067] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1018(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1025 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1019] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1019] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1022 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1019] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1018 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2040 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2041 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1068(Jurisdiction934): - pass - - -class Disclosure1023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1068] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1026 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1020] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1020] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1023 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1020] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1019 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2042 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2043 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1069(Jurisdiction934): - pass - - -class Disclosure1024(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1069] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1027 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1021] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1021] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1024 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1021] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1020 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2044 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2045 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1070(Jurisdiction934): - pass - - -class Disclosure1025(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1070] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1028 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1022] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1022] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1025 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1022] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1021 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2046 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2047 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1071(Jurisdiction934): - pass - - -class Disclosure1026(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1071] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1029 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1023] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1023] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1026 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1023] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1022 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1024(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2048 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1024(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2049 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1072(Jurisdiction934): - pass - - -class Disclosure1027(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1072] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1030 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1024] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1024] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1027 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1024] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1023 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1025(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2050 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1025(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2051 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1073(Jurisdiction934): - pass - - -class Disclosure1028(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1073] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1024(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1031 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1025] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1025] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1028 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1025] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1024 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1026(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2052 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1026(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2053 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1074(Jurisdiction934): - pass - - -class Disclosure1029(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1074] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1025(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1032 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1026] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1026] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1029 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1026] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1025 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1027(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2054 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1027(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2055 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1075(Jurisdiction934): - pass - - -class Disclosure1030(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1075] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1026(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1033 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1027] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1027] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1030 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1027] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1026 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1028(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2056 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1028(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2057 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1076(Jurisdiction934): - pass - - -class Disclosure1031(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1076] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1027(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1034 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1028] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1028] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1031 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1028] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1027 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1029(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2058 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1029(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2059 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1077(Jurisdiction934): - pass - - -class Disclosure1032(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1077] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1028(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1035 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1029] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1029] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1032 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1029] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1028 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1030(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2060 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1030(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2061 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1078(Jurisdiction934): - pass - - -class Disclosure1033(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1078] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1029(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1036 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1030] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1030] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1033 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1030] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1029 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure38(RequiredDisclosure): - pass - - -class Compliance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure38] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets52117(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset38] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance38 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets52118(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping41] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1031(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2062 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1031(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2063 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1080(Jurisdiction934): - pass - - -class Disclosure1034(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1080] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1030(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1037 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1031] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1031] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1034 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1031] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization38 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1030 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1032(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2064 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1032(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2065 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1081(Jurisdiction934): - pass - - -class Disclosure1035(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1081] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1031(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1038 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1032] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1032] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1035 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1032] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1031 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1033(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2066 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1033(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2067 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1082(Jurisdiction934): - pass - - -class Disclosure1036(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1082] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1032(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1039 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1033] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1033] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1036 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1033] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1032 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1034(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2068 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1034(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2069 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1083(Jurisdiction934): - pass - - -class Disclosure1037(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1083] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1033(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1040 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1034] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1034] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1037 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1034] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1033 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1035(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2070 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1035(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2071 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1084(Jurisdiction934): - pass - - -class Disclosure1038(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1084] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1034(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1041 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1035] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1035] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1038 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1035] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media74 | Media75, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl37 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1034 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1036(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2072 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1036(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2073 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1085(Jurisdiction934): - pass - - -class Disclosure1039(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1085] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1035(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1042 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1036] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1036] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1039 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1036] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1035 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1037(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2074 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1037(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2075 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1086(Jurisdiction934): - pass - - -class Disclosure1040(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1086] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1036(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1043 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1037] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1037] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1040 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1037] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1036 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1038(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2076 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1038(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2077 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1087(Jurisdiction934): - pass - - -class Disclosure1041(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1087] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1037(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1044 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1038] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1038] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1041 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1038] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1037 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets5211( - RootModel[ - Assets5212 - | Assets5213 - | Assets5214 - | Assets5215 - | Assets5216 - | Assets5217 - | Assets5218 - | Assets5219 - | Assets52110 - | Assets52111 - | Assets52112 - | Assets52113 - | Assets52114 - | Assets52115 - | Assets490 - | Assets52117 - | Assets52118 - | Assets52119 - | Assets52120 - | Assets52121 - | Assets52122 - | Assets52123 - ] -): - root: Annotated[ - Assets5212 - | Assets5213 - | Assets5214 - | Assets5215 - | Assets5216 - | Assets5217 - | Assets5218 - | Assets5219 - | Assets52110 - | Assets52111 - | Assets52112 - | Assets52113 - | Assets52114 - | Assets52115 - | Assets490 - | Assets52117 - | Assets52118 - | Assets52119 - | Assets52120 - | Assets52121 - | Assets52122 - | Assets52123, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets521(RootModel[list[Assets5211]]): - root: Annotated[list[Assets5211], Field(min_length=1)] - - -class EmbeddedProvenanceItem1039(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2078 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1039(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2079 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1088(Jurisdiction934): - pass - - -class Disclosure1042(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1088] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1038(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1045 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1039] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1039] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1042 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1039] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1038 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo148 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand51(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride147 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right21(Right20): - pass - - -class EmbeddedProvenanceItem1040(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2080 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1040(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2081 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1089(Jurisdiction934): - pass - - -class Disclosure1043(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1089] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1039(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1046 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1040] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1040] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1043 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1040] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets499 - | Assets500 - | Assets501 - | Assets502 - | Assets503 - | Assets504 - | Assets505 - | Assets506 - | Assets507 - | Assets508 - | Assets509 - | Assets510 - | Assets511 - | Assets512 - | Assets513 - | Assets514 - | Assets515 - | Assets516 - | Assets517 - | Assets518 - | Assets519 - | Assets520 - | Assets521, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand51 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right21] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1039 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result1010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError54 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig57 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest | CreativeManifest14, - Field( - description='The generated or transformed creative manifest', title='Creative Manifest' - ), - ] - build_variant_id: Annotated[ - str | None, - Field( - description='Leaf handle for this produced creative — present when the agent supports refinement (creative.supports_refinement). Pass it as refine_from_build_variant_id to refine this build. Same namespace as BuildCreativeVariantSuccess leaves; distinct from served variant_id / preview_id. On the canonical promotion path, this value becomes the creative_id when the produced leaf is trafficked or added to the library.' - ), - ] = None - recipe_hash: Annotated[ - str | None, - Field( - description='Optional agent-computed, opaque, agent-scoped identity for the build-determining inputs that produced this creative. Stable for identical inputs as defined by the agent, and comparable only within the same agent. ETag-style semantics: the protocol defines the field and contract, not the hash algorithm or canonical input set. Identifies generative-input identity, not output equality, legal/disclosure equivalence, or the build-to-delivery join.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when generated asset URLs in the manifest expire. Set to the earliest expiration across all generated assets. Re-build the creative after this time to get fresh URLs.' - ), - ] = None - preview: Annotated[ - Preview4 | None, - Field( - description='Preview renders included when the request set include_preview to true and the agent supports it. Contains the same content fields as a preview_creative single response (previews, interactive_url, expires_at) minus the response_type discriminator, so clients can reuse the same preview rendering logic.' - ), - ] = None - preview_error: Annotated[ - PreviewError | None, - Field( - description="When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Distinguishes 'agent does not support inline preview' (preview and preview_error both absent) from 'preview generation failed' (preview absent, preview_error present). Omitted when preview succeeded or was not requested.", - title='Error', - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate card pricing option was applied for this build. Present when the creative agent charges for its services. Pass this in report_usage to identify which pricing option was applied.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Cost incurred for this build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time rather than build time.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.', - title='Creative Consumption', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig58(PushNotificationConfig52): - pass - - -class EmbeddedProvenanceItem1041(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2082 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1041(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2083 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1090(Jurisdiction934): - pass - - -class Disclosure1044(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1090] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1040(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1047 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1041] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1041] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1044 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1041] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1040 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1042(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2084 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1042(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2085 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1091(Jurisdiction934): - pass - - -class Disclosure1045(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1091] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1041(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1048 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1042] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1042] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1045 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1042] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1041 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1043(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2086 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1043(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2087 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1092(Jurisdiction934): - pass - - -class Disclosure1046(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1092] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1042(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1049 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1043] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1043] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1046 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1043] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1042 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1044(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2088 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1044(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2089 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1093(Jurisdiction934): - pass - - -class Disclosure1047(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1093] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1043(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1050 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1044] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1044] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1047 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1044] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1043 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1045(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2090 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1045(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2091 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1094(Jurisdiction934): - pass - - -class Disclosure1048(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1094] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1044(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1051 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1045] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1045] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1048 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1045] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1044 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1046(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2092 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1046(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2093 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1095(Jurisdiction934): - pass - - -class Disclosure1049(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1095] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1045(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1052 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1046] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1046] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1049 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1046] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1045 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1047(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2094 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1047(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2095 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1096(Jurisdiction934): - pass - - -class Disclosure1050(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1096] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1046(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1053 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1047] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1047] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1050 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1047] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1046 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1048(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2096 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1048(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2097 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1097(Jurisdiction934): - pass - - -class Disclosure1051(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1097] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1047(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1054 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1048] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1048] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1051 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1048] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1047 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1049(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2098 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1049(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2099 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1098(Jurisdiction934): - pass - - -class Disclosure1052(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1098] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1048(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1055 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1049] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1049] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1052 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1049] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets530(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1048 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1050(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1050(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1099(Jurisdiction934): - pass - - -class Disclosure1053(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1099] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1049(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1056 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1050] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1050] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1053 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1050] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets531(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1049 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1051(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1051(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1100(Jurisdiction934): - pass - - -class Disclosure1054(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1100] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1050(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1057 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1051] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1051] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1054 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1051] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets532(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1050 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1052(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1052(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1101(Jurisdiction934): - pass - - -class Disclosure1055(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1101] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1051(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1058 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1052] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1052] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1055 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1052] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets533(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1051 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1053(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1053(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1102(Jurisdiction934): - pass - - -class Disclosure1056(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1102] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1052(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1059 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1053] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1053] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1056 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1053] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets534(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1052 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1054(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1054(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1103(Jurisdiction934): - pass - - -class Disclosure1057(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1103] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1053(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1060 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1054] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1054] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1057 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1054] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets535(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1053 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets536(Assets490): - pass - - -class RequiredDisclosure39(RequiredDisclosure): - pass - - -class Compliance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure39] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets537(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset39] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance39 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets538(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping42] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1055(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1055(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1105(Jurisdiction934): - pass - - -class Disclosure1058(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1105] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1054(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1061 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1055] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1055] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1058 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1055] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets539(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization39 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1054 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1056(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1056(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1106(Jurisdiction934): - pass - - -class Disclosure1059(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1106] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1055(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1062 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1056] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1056] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1059 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1056] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1055 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1057(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1057(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1107(Jurisdiction934): - pass - - -class Disclosure1060(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1107] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1056(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1063 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1057] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1057] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1060 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1057] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1056 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1058(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1058(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1108(Jurisdiction934): - pass - - -class Disclosure1061(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1108] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1057(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1064 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1058] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1058] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1061 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1058] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1057 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1059(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1059(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1109(Jurisdiction934): - pass - - -class Disclosure1062(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1109] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1058(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1065 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1059] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1059] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1062 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1059] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets540(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media76 | Media77, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl38 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1058 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1060(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1060(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1110(Jurisdiction934): - pass - - -class Disclosure1063(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1110] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1059(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1066 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1060] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1060] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1063 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1060] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets541(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1059 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1061(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1061(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1111(Jurisdiction934): - pass - - -class Disclosure1064(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1111] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1060(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1067 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1061] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1061] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1064 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1061] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets542(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1060 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1062(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1062(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1112(Jurisdiction934): - pass - - -class Disclosure1065(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1112] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1061(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1068 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1062] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1062] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1065 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1062] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets543(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1061 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1063(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1063(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1113(Jurisdiction934): - pass - - -class Disclosure1066(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1113] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1062(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1069 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1063] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1063] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1066 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1063] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1062 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1064(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1064(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1114(Jurisdiction934): - pass - - -class Disclosure1067(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1114] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1063(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1070 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1064] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1064] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1067 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1064] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1063 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1065(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1065(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1115(Jurisdiction934): - pass - - -class Disclosure1068(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1115] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1064(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1071 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1065] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1065] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1068 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1065] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1064 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1066(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1066(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1116(Jurisdiction934): - pass - - -class Disclosure1069(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1116] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1065(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1072 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1066] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1066] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1069 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1066] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5445(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1065 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1067(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1067(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1117(Jurisdiction934): - pass - - -class Disclosure1070(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1117] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1066(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1073 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1067] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1067] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1070 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1067] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5446(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1066 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1068(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1068(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1118(Jurisdiction934): - pass - - -class Disclosure1071(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1118] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1067(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1074 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1068] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1068] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1071 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1068] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1067 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1069(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1069(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1119(Jurisdiction934): - pass - - -class Disclosure1072(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1119] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1068(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1075 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1069] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1069] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1072 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1069] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1068 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1070(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1070(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1120(Jurisdiction934): - pass - - -class Disclosure1073(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1120] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1069(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1076 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1070] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1070] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1073 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1070] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1069 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1071(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1071(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1121(Jurisdiction934): - pass - - -class Disclosure1074(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1121] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1070(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1077 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1071] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1071] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1074 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1071] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1070 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1072(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1072(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1122(Jurisdiction934): - pass - - -class Disclosure1075(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1122] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1071(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1078 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1072] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1072] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1075 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1072] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1071 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1073(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1073(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1123(Jurisdiction934): - pass - - -class Disclosure1076(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1123] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1072(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1079 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1073] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1073] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1076 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1073] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1072 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1074(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1074(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1124(Jurisdiction934): - pass - - -class Disclosure1077(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1124] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1073(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1080 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1074] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1074] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1077 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1074] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1073 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1075(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1075(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1125(Jurisdiction934): - pass - - -class Disclosure1078(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1125] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1074(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1081 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1075] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1075] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1078 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1075] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1074 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1076(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1076(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1126(Jurisdiction934): - pass - - -class Disclosure1079(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1126] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1075(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1082 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1076] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1076] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1079 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1076] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1075 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure40(RequiredDisclosure): - pass - - -class Compliance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure40] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets54417(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset40] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance40 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets54418(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping43] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1077(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1077(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1128(Jurisdiction934): - pass - - -class Disclosure1080(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1128] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1076(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1083 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1077] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1077] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1080 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1077] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization40 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1076 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1078(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1078(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1129(Jurisdiction934): - pass - - -class Disclosure1081(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1129] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1077(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1084 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1078] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1078] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1081 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1078] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1077 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1079(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1079(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1130(Jurisdiction934): - pass - - -class Disclosure1082(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1130] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1078(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1085 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1079] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1079] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1082 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1079] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1078 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1080(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1080(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1131(Jurisdiction934): - pass - - -class Disclosure1083(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1131] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1079(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1086 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1080] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1080] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1083 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1080] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1079 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1081(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1081(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1132(Jurisdiction934): - pass - - -class Disclosure1084(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1132] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1080(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1087 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1081] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1081] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1084 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1081] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media78 | Media79, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl39 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1080 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1082(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1082(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1133(Jurisdiction934): - pass - - -class Disclosure1085(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1133] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1081(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1088 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1082] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1082] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1085 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1082] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1081 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1083(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1083(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1134(Jurisdiction934): - pass - - -class Disclosure1086(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1134] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1082(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1089 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1083] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1083] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1086 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1083] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1082 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1084(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1084(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1135(Jurisdiction934): - pass - - -class Disclosure1087(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1135] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1083(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1090 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1084] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1084] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1087 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1084] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1083 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets5441( - RootModel[ - Assets5442 - | Assets5443 - | Assets5444 - | Assets5445 - | Assets5446 - | Assets5447 - | Assets5448 - | Assets5449 - | Assets54410 - | Assets54411 - | Assets54412 - | Assets54413 - | Assets54414 - | Assets54415 - | Assets490 - | Assets54417 - | Assets54418 - | Assets54419 - | Assets54420 - | Assets54421 - | Assets54422 - | Assets54423 - ] -): - root: Annotated[ - Assets5442 - | Assets5443 - | Assets5444 - | Assets5445 - | Assets5446 - | Assets5447 - | Assets5448 - | Assets5449 - | Assets54410 - | Assets54411 - | Assets54412 - | Assets54413 - | Assets54414 - | Assets54415 - | Assets490 - | Assets54417 - | Assets54418 - | Assets54419 - | Assets54420 - | Assets54421 - | Assets54422 - | Assets54423, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets544(RootModel[list[Assets5441]]): - root: Annotated[list[Assets5441], Field(min_length=1)] - - -class EmbeddedProvenanceItem1085(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1085(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1136(Jurisdiction934): - pass - - -class Disclosure1088(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1136] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1084(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1091 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1085] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1085] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1088 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1085] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1084 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo149 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand52(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride148 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right22(Right20): - pass - - -class EmbeddedProvenanceItem1086(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1086(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1137(Jurisdiction934): - pass - - -class Disclosure1089(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1137] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1085(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1092 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1086] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1086] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1089 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1086] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifests(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets522 - | Assets523 - | Assets524 - | Assets525 - | Assets526 - | Assets527 - | Assets528 - | Assets529 - | Assets530 - | Assets531 - | Assets532 - | Assets533 - | Assets534 - | Assets535 - | Assets536 - | Assets537 - | Assets538 - | Assets539 - | Assets540 - | Assets541 - | Assets542 - | Assets543 - | Assets544, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand52 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right22] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1085 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem1087(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1087(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1138(Jurisdiction934): - pass - - -class Disclosure1090(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1138] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1086(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1093 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1087] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1087] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1090 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1087] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets545(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1086 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1088(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1088(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1139(Jurisdiction934): - pass - - -class Disclosure1091(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1139] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1087(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1094 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1088] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1088] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1091 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1088] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets546(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1087 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1089(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1089(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1140(Jurisdiction934): - pass - - -class Disclosure1092(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1140] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1088(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1095 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1089] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1089] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1092 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1089] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets547(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1088 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1090(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1090(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1141(Jurisdiction934): - pass - - -class Disclosure1093(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1141] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1089(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1096 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1090] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1090] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1093 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1090] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets548(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1089 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1091(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1091(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1142(Jurisdiction934): - pass - - -class Disclosure1094(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1142] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1090(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1097 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1091] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1091] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1094 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1091] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets549(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1090 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1092(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1092(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1143(Jurisdiction934): - pass - - -class Disclosure1095(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1143] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1091(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1098 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1092] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1092] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1095 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1092] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets550(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1091 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1093(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1093(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1144(Jurisdiction934): - pass - - -class Disclosure1096(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1144] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1092(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1099 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1093] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1093] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1096 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1093] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets551(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1092 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1094(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1094(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1145(Jurisdiction934): - pass - - -class Disclosure1097(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1145] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1093(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1100 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1094] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1094] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1097 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1094] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets552(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1093 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1095(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1095(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1146(Jurisdiction934): - pass - - -class Disclosure1098(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1146] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1094(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1101 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1095] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1095] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1098 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1095] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets553(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1094 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1096(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2192 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1096(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2193 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1147(Jurisdiction934): - pass - - -class Disclosure1099(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1147] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1095(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1102 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1096] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1096] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1099 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1096] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets554(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1095 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1097(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2194 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1097(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2195 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1148(Jurisdiction934): - pass - - -class Disclosure1100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1148] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1096(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1103 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1097] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1097] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1100 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1097] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets555(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1096 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1098(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2196 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1098(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2197 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1149(Jurisdiction934): - pass - - -class Disclosure1101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1149] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1097(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1104 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1098] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1098] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1101 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1098] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets556(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1097 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1099(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2198 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1099(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2199 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1150(Jurisdiction934): - pass - - -class Disclosure1102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1150] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1098(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1105 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1099] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1099] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1102 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1099] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets557(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1098 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2200 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2201 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1151(Jurisdiction934): - pass - - -class Disclosure1103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1151] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1099(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1106 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1100] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1100] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1103 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1100] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets558(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1099 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets559(Assets490): - pass - - -class RequiredDisclosure41(RequiredDisclosure): - pass - - -class Compliance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure41] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets560(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset41] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance41 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets561(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping44] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2202 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2203 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1153(Jurisdiction934): - pass - - -class Disclosure1104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1153] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1107 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1101] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1101] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1104 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1101] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets562(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization41 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1100 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2204 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2205 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1154(Jurisdiction934): - pass - - -class Disclosure1105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1154] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1108 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1102] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1102] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1105 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1102] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1101 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2206 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2207 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1155(Jurisdiction934): - pass - - -class Disclosure1106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1155] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1109 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1103] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1103] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1106 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1103] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1102 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2208 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2209 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1156(Jurisdiction934): - pass - - -class Disclosure1107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1156] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1110 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1104] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1104] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1107 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1104] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1103 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2210 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2211 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1157(Jurisdiction934): - pass - - -class Disclosure1108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1157] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1111 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1105] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1105] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1108 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1105] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets563(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media80 | Media81, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl40 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1104 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2212 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2213 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1158(Jurisdiction934): - pass - - -class Disclosure1109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1158] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1112 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1106] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1106] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1109 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1106] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets564(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1105 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2214 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2215 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1159(Jurisdiction934): - pass - - -class Disclosure1110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1159] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1113 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1107] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1107] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1110 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1107] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets565(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1106 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2216 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2217 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1160(Jurisdiction934): - pass - - -class Disclosure1111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1160] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1114 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1108] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1108] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1111 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1108] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets566(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1107 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2218 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2219 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1161(Jurisdiction934): - pass - - -class Disclosure1112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1161] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1115 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1109] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1109] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1112 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1109] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1108 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2220 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2221 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1162(Jurisdiction934): - pass - - -class Disclosure1113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1162] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1116 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1110] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1110] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1113 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1110] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1109 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2222 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2223 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1163(Jurisdiction934): - pass - - -class Disclosure1114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1163] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1117 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1111] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1111] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1114 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1111] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5674(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1110 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2224 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2225 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1164(Jurisdiction934): - pass - - -class Disclosure1115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1164] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1118 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1112] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1112] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1115 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1112] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5675(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1111 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2226 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2227 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1165(Jurisdiction934): - pass - - -class Disclosure1116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1165] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1119 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1113] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1113] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1116 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1113] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5676(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1112 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2228 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2229 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1166(Jurisdiction934): - pass - - -class Disclosure1117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1166] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1120 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1114] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1114] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1117 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1114] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5677(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1113 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2230 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2231 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1167(Jurisdiction934): - pass - - -class Disclosure1118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1167] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1121 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1115] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1115] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1118 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1115] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5678(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1114 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2232 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2233 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1168(Jurisdiction934): - pass - - -class Disclosure1119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1168] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1122 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1116] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1116] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1119 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1116] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5679(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1115 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2234 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2235 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1169(Jurisdiction934): - pass - - -class Disclosure1120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1169] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1123 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1117] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1117] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1120 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1117] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1116 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2236 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2237 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1170(Jurisdiction934): - pass - - -class Disclosure1121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1170] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1124 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1118] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1118] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1121 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1118] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1117 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2238 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2239 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1171(Jurisdiction934): - pass - - -class Disclosure1122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1171] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1125 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1119] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1119] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1122 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1119] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1118 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2240 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2241 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1172(Jurisdiction934): - pass - - -class Disclosure1123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1172] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1126 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1120] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1120] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1123 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1120] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1119 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2242 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2243 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1173(Jurisdiction934): - pass - - -class Disclosure1124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1173] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1127 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1121] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1121] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1124 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1121] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1120 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2244 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2245 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1174(Jurisdiction934): - pass - - -class Disclosure1125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1174] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1128 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1122] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1122] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1125 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1122] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1121 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure42(RequiredDisclosure): - pass - - -class Compliance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure42] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets56717(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset42] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance42 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets56718(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping45] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2246 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2247 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1176(Jurisdiction934): - pass - - -class Disclosure1126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1176] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1129 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1123] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1123] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1126 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1123] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization42 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1122 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2248 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2249 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1177(Jurisdiction934): - pass - - -class Disclosure1127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1177] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1130 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1124] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1124] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1127 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1124] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1123 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2250 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2251 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1178(Jurisdiction934): - pass - - -class Disclosure1128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1178] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1131 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1125] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1125] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1128 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1125] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1124 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2252 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2253 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1179(Jurisdiction934): - pass - - -class Disclosure1129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1179] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1132 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1126] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1126] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1129 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1126] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1125 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2254 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2255 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1180(Jurisdiction934): - pass - - -class Disclosure1130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1180] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1133 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1127] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1127] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1130 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1127] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media82 | Media83, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl41 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1126 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2256 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2257 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1181(Jurisdiction934): - pass - - -class Disclosure1131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1181] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1134 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1128] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1128] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1131 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1128] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1127 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2258 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2259 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1182(Jurisdiction934): - pass - - -class Disclosure1132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1182] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1135 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1129] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1129] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1132 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1129] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1128 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2260 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2261 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1183(Jurisdiction934): - pass - - -class Disclosure1133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1183] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1136 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1130] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1130] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1133 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1130] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1129 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets5671( - RootModel[ - Assets5672 - | Assets5673 - | Assets5674 - | Assets5675 - | Assets5676 - | Assets5677 - | Assets5678 - | Assets5679 - | Assets56710 - | Assets56711 - | Assets56712 - | Assets56713 - | Assets56714 - | Assets56715 - | Assets490 - | Assets56717 - | Assets56718 - | Assets56719 - | Assets56720 - | Assets56721 - | Assets56722 - | Assets56723 - ] -): - root: Annotated[ - Assets5672 - | Assets5673 - | Assets5674 - | Assets5675 - | Assets5676 - | Assets5677 - | Assets5678 - | Assets5679 - | Assets56710 - | Assets56711 - | Assets56712 - | Assets56713 - | Assets56714 - | Assets56715 - | Assets490 - | Assets56717 - | Assets56718 - | Assets56719 - | Assets56720 - | Assets56721 - | Assets56722 - | Assets56723, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets567(RootModel[list[Assets5671]]): - root: Annotated[list[Assets5671], Field(min_length=1)] - - -class EmbeddedProvenanceItem1131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2262 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2263 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1184(Jurisdiction934): - pass - - -class Disclosure1134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1184] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1137 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1131] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1131] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1134 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1131] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1130 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo150 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand53(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride149 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right23(Right20): - pass - - -class EmbeddedProvenanceItem1132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2264 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2265 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1185(Jurisdiction934): - pass - - -class Disclosure1135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1185] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1138 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1132] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1132] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1135 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1132] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifests3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets545 - | Assets546 - | Assets547 - | Assets548 - | Assets549 - | Assets550 - | Assets551 - | Assets552 - | Assets553 - | Assets554 - | Assets555 - | Assets556 - | Assets557 - | Assets558 - | Assets559 - | Assets560 - | Assets561 - | Assets562 - | Assets563 - | Assets564 - | Assets565 - | Assets566 - | Assets567, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand53 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right23] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1131 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result1103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError55 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig58 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creative_manifests: Annotated[ - list[CreativeManifests | CreativeManifests3], - Field( - description='Array of generated creative manifests, one per requested format. Each manifest contains its own format_id identifying which format it was generated for.', - min_length=1, - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the earliest generated asset URL expires across all manifests. Re-build after this time to get fresh URLs.' - ), - ] = None - preview: Annotated[ - Preview6 | None, - Field( - description='Preview renders included when the request set include_preview to true and the agent supports it. Contains one default preview per requested format. preview_inputs is ignored for multi-format requests.' - ), - ] = None - preview_error: Annotated[ - PreviewError3 | None, - Field( - description='When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Omitted when preview succeeded or was not requested.', - title='Error', - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate card pricing option was applied for this build. Represents the total cost of the entire multi-format build call. Present when the creative agent charges for its services.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Total cost incurred for this multi-format build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card.', - title='Creative Consumption', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig59(PushNotificationConfig52): - pass - - -class EmbeddedProvenanceItem1133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2266 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2267 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1186(Jurisdiction934): - pass - - -class Disclosure1136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1186] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1139 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1133] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1133] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1136 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1133] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets568(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1132 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2268 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2269 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1187(Jurisdiction934): - pass - - -class Disclosure1137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1187] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1140 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1134] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1134] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1137 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1134] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets569(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1133 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2270 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2271 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1188(Jurisdiction934): - pass - - -class Disclosure1138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1188] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1141 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1135] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1135] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1138 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1135] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets570(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1134 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2272 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2273 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1189(Jurisdiction934): - pass - - -class Disclosure1139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1189] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1142 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1136] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1136] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1139 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1136] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets571(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1135 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2274 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2275 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1190(Jurisdiction934): - pass - - -class Disclosure1140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1190] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1143 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1137] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1137] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1140 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1137] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets572(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1136 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2276 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2277 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1191(Jurisdiction934): - pass - - -class Disclosure1141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1191] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1144 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1138] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1138] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1141 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1138] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets573(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1137 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2278 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2279 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1192(Jurisdiction934): - pass - - -class Disclosure1142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1192] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1145 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1139] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1139] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1142 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1139] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets574(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1138 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2280 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2281 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1193(Jurisdiction934): - pass - - -class Disclosure1143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1193] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1146 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1140] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1140] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1143 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1140] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets575(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1139 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2282 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2283 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1194(Jurisdiction934): - pass - - -class Disclosure1144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1194] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1147 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1141] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1141] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1144 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1141] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets576(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1140 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2284 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2285 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1195(Jurisdiction934): - pass - - -class Disclosure1145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1195] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1148 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1142] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1142] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1145 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1142] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1141 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2286 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2287 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1196(Jurisdiction934): - pass - - -class Disclosure1146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1196] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1149 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1143] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1143] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1146 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1143] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1142 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2288 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2289 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1197(Jurisdiction934): - pass - - -class Disclosure1147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1197] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1150 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1144] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1144] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1147 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1144] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1143 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2290 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2291 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1198(Jurisdiction934): - pass - - -class Disclosure1148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1198] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1151 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1145] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1145] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1148 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1145] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1144 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2292 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2293 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1199(Jurisdiction934): - pass - - -class Disclosure1149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1199] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1152 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1146] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1146] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1149 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1146] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1145 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets582(Assets490): - pass - - -class RequiredDisclosure43(RequiredDisclosure): - pass - - -class Compliance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure43] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets583(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset43] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance43 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets584(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping46] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2294 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2295 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1201(Jurisdiction934): - pass - - -class Disclosure1150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1201] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1153 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1147] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1147] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1150 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1147] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization43 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1146 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2296 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2297 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1202(Jurisdiction934): - pass - - -class Disclosure1151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1202] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1154 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1148] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1148] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1151 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1148] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1147 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2298 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2299 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1203(Jurisdiction934): - pass - - -class Disclosure1152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1203] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1155 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1149] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1149] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1152 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1149] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1148 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2300 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2301 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1204(Jurisdiction934): - pass - - -class Disclosure1153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1204] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1156 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1150] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1150] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1153 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1150] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1149 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2302 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2303 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1205(Jurisdiction934): - pass - - -class Disclosure1154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1205] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1157 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1151] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1151] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1154 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1151] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media84 | Media85, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl42 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1150 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2304 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2305 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1206(Jurisdiction934): - pass - - -class Disclosure1155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1206] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1158 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1152] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1152] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1155 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1152] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1151 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2306 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2307 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1207(Jurisdiction934): - pass - - -class Disclosure1156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1207] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1159 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1153] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1153] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1156 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1153] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1152 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2308 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2309 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1208(Jurisdiction934): - pass - - -class Disclosure1157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1208] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1160 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1154] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1154] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1157 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1154] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1153 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2310 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2311 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1209(Jurisdiction934): - pass - - -class Disclosure1158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1209] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1161 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1155] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1155] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1158 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1155] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5902(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1154 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2312 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2313 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1210(Jurisdiction934): - pass - - -class Disclosure1159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1210] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1162 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1156] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1156] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1159 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1156] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5903(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1155 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2314 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2315 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1211(Jurisdiction934): - pass - - -class Disclosure1160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1211] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1163 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1157] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1157] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1160 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1157] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5904(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1156 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2316 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2317 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1212(Jurisdiction934): - pass - - -class Disclosure1161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1212] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1164 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1158] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1158] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1161 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1158] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5905(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1157 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2318 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2319 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1213(Jurisdiction934): - pass - - -class Disclosure1162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1213] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1165 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1159] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1159] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1162 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1159] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5906(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1158 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2320 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2321 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1214(Jurisdiction934): - pass - - -class Disclosure1163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1214] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1166 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1160] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1160] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1163 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1160] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5907(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1159 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2322 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2323 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1215(Jurisdiction934): - pass - - -class Disclosure1164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1215] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1167 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1161] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1161] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1164 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1161] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5908(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1160 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2324 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2325 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1216(Jurisdiction934): - pass - - -class Disclosure1165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1216] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1168 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1162] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1162] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1165 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1162] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5909(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1161 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2326 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2327 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1217(Jurisdiction934): - pass - - -class Disclosure1166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1217] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1169 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1163] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1163] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1166 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1163] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1162 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2328 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2329 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1218(Jurisdiction934): - pass - - -class Disclosure1167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1218] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1170 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1164] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1164] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1167 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1164] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1163 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2330 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2331 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1219(Jurisdiction934): - pass - - -class Disclosure1168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1219] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1171 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1165] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1165] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1168 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1165] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1164 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2332 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2333 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1220(Jurisdiction934): - pass - - -class Disclosure1169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1220] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1172 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1166] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1166] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1169 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1166] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1165 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2334 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2335 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1221(Jurisdiction934): - pass - - -class Disclosure1170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1221] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1173 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1167] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1167] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1170 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1167] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1166 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2336 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2337 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1222(Jurisdiction934): - pass - - -class Disclosure1171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1222] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1174 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1168] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1168] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1171 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1168] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1167 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure44(RequiredDisclosure): - pass - - -class Compliance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure44] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets59017(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset44] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance44 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets59018(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping47] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2338 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2339 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1224(Jurisdiction934): - pass - - -class Disclosure1172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1224] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1175 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1169] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1169] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1172 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1169] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization44 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1168 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2340 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2341 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1225(Jurisdiction934): - pass - - -class Disclosure1173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1225] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1176 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1170] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1170] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1173 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1170] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1169 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2342 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2343 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1226(Jurisdiction934): - pass - - -class Disclosure1174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1226] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1177 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1171] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1171] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1174 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1171] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1170 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2344 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2345 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1227(Jurisdiction934): - pass - - -class Disclosure1175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1227] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1178 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1172] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1172] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1175 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1172] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1171 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2346 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2347 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1228(Jurisdiction934): - pass - - -class Disclosure1176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1228] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1179 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1173] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1173] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1176 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1173] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media86 | Media87, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl43 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1172 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2348 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2349 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1229(Jurisdiction934): - pass - - -class Disclosure1177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1229] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1180 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1174] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1174] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1177 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1174] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1173 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2350 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2351 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1230(Jurisdiction934): - pass - - -class Disclosure1178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1230] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1181 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1175] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1175] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1178 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1175] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1174 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2352 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2353 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1231(Jurisdiction934): - pass - - -class Disclosure1179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1231] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1182 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1176] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1176] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1179 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1176] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1175 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets5901( - RootModel[ - Assets5902 - | Assets5903 - | Assets5904 - | Assets5905 - | Assets5906 - | Assets5907 - | Assets5908 - | Assets5909 - | Assets59010 - | Assets59011 - | Assets59012 - | Assets59013 - | Assets59014 - | Assets59015 - | Assets490 - | Assets59017 - | Assets59018 - | Assets59019 - | Assets59020 - | Assets59021 - | Assets59022 - | Assets59023 - ] -): - root: Annotated[ - Assets5902 - | Assets5903 - | Assets5904 - | Assets5905 - | Assets5906 - | Assets5907 - | Assets5908 - | Assets5909 - | Assets59010 - | Assets59011 - | Assets59012 - | Assets59013 - | Assets59014 - | Assets59015 - | Assets490 - | Assets59017 - | Assets59018 - | Assets59019 - | Assets59020 - | Assets59021 - | Assets59022 - | Assets59023, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets590(RootModel[list[Assets5901]]): - root: Annotated[list[Assets5901], Field(min_length=1)] - - -class EmbeddedProvenanceItem1177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2354 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2355 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1232(Jurisdiction934): - pass - - -class Disclosure1180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1232] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1183 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1177] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1177] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1180 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1177] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1176 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo151 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand54(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride150 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right24(Right20): - pass - - -class EmbeddedProvenanceItem1178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2356 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2357 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1233(Jurisdiction934): - pass - - -class Disclosure1181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1233] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1184 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1178] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1178] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1181 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1178] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets568 - | Assets569 - | Assets570 - | Assets571 - | Assets572 - | Assets573 - | Assets574 - | Assets575 - | Assets576 - | Assets577 - | Assets578 - | Assets579 - | Assets580 - | Assets581 - | Assets582 - | Assets583 - | Assets584 - | Assets585 - | Assets586 - | Assets587 - | Assets588 - | Assets589 - | Assets590, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand54 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right24] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1177 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem1179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2358 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2359 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1234(Jurisdiction934): - pass - - -class Disclosure1182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1234] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1185 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1179] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1179] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1182 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1179] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1178 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2360 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2361 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1235(Jurisdiction934): - pass - - -class Disclosure1183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1235] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1186 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1180] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1180] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1183 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1180] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1179 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2362 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2363 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1236(Jurisdiction934): - pass - - -class Disclosure1184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1236] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1187 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1181] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1181] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1184 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1181] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1180 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2364 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2365 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1237(Jurisdiction934): - pass - - -class Disclosure1185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1237] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1188 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1182] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1182] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1185 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1182] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1181 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2366 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2367 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1238(Jurisdiction934): - pass - - -class Disclosure1186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1238] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1189 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1183] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1183] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1186 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1183] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1182 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2368 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2369 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1239(Jurisdiction934): - pass - - -class Disclosure1187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1239] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1190 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1184] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1184] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1187 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1184] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets596(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1183 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2370 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2371 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1240(Jurisdiction934): - pass - - -class Disclosure1188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1240] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1191 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1185] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1185] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1188 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1185] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets597(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1184 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2372 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2373 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1241(Jurisdiction934): - pass - - -class Disclosure1189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1241] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1192 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1186] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1186] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1189 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1186] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets598(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1185 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2374 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2375 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1242(Jurisdiction934): - pass - - -class Disclosure1190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1242] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1193 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1187] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1187] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1190 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1187] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets599(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1186 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2376 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2377 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1243(Jurisdiction934): - pass - - -class Disclosure1191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1243] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1194 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1188] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1188] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1191 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1188] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets600(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1187 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2378 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2379 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1244(Jurisdiction934): - pass - - -class Disclosure1192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1244] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1195 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1189] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1189] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1192 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1189] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets601(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1188 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2380 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2381 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1245(Jurisdiction934): - pass - - -class Disclosure1193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1245] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1196 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1190] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1190] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1193 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1190] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1189 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2382 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2383 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1246(Jurisdiction934): - pass - - -class Disclosure1194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1246] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1197 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1191] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1191] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1194 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1191] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets603(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1190 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2384 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2385 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1247(Jurisdiction934): - pass - - -class Disclosure1195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1247] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1198 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1192] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1192] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1195 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1192] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets604(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1191 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets605(Assets490): - pass - - -class RequiredDisclosure45(RequiredDisclosure): - pass - - -class Compliance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure45] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets606(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset45] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance45 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets607(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping48] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2386 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2387 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1249(Jurisdiction934): - pass - - -class Disclosure1196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1249] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1199 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1193] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1193] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1196 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1193] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets608(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization45 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1192 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2388 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2389 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1250(Jurisdiction934): - pass - - -class Disclosure1197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1250] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1200 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1194] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1194] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1197 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1194] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1193 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2390 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2391 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1251(Jurisdiction934): - pass - - -class Disclosure1198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1251] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1201 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1195] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1195] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1198 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1195] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1194 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2392 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2393 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1252(Jurisdiction934): - pass - - -class Disclosure1199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1252] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1202 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1196] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1196] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1199 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1196] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1195 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2394 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2395 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1253(Jurisdiction934): - pass - - -class Disclosure1200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1253] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1203 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1197] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1197] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1200 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1197] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media88 | Media89, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl44 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1196 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2396 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2397 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1254(Jurisdiction934): - pass - - -class Disclosure1201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1254] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1204 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1198] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1198] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1201 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1198] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1197 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2398 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2399 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1255(Jurisdiction934): - pass - - -class Disclosure1202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1255] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1205 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1199] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1199] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1202 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1199] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1198 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2400 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2401 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1256(Jurisdiction934): - pass - - -class Disclosure1203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1256] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1206 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1200] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1200] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1203 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1200] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1199 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2402 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2403 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1257(Jurisdiction934): - pass - - -class Disclosure1204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1257] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1207 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1201] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1201] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1204 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1201] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1200 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2404 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2405 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1258(Jurisdiction934): - pass - - -class Disclosure1205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1258] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1208 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1202] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1202] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1205 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1202] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1201 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2406 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2407 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1259(Jurisdiction934): - pass - - -class Disclosure1206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1259] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1209 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1203] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1203] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1206 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1203] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1202 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2408 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2409 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1260(Jurisdiction934): - pass - - -class Disclosure1207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1260] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1210 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1204] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1204] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1207 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1204] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1203 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2410 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2411 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1261(Jurisdiction934): - pass - - -class Disclosure1208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1261] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1211 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1205] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1205] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1208 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1205] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1204 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2412 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2413 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1262(Jurisdiction934): - pass - - -class Disclosure1209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1262] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1212 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1206] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1206] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1209 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1206] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1205 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2414 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2415 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1263(Jurisdiction934): - pass - - -class Disclosure1210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1263] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1213 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1207] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1207] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1210 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1207] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1206 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2416 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2417 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1264(Jurisdiction934): - pass - - -class Disclosure1211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1264] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1214 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1208] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1208] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1211 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1208] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1207 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2418 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2419 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1265(Jurisdiction934): - pass - - -class Disclosure1212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1265] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1215 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1209] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1209] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1212 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1209] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1208 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2420 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2421 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1266(Jurisdiction934): - pass - - -class Disclosure1213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1266] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1216 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1210] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1210] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1213 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1210] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1209 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2422 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2423 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1267(Jurisdiction934): - pass - - -class Disclosure1214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1267] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1217 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1211] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1211] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1214 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1211] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1210 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2424 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2425 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1268(Jurisdiction934): - pass - - -class Disclosure1215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1268] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1218 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1212] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1212] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1215 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1212] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1211 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2426 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2427 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1269(Jurisdiction934): - pass - - -class Disclosure1216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1269] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1219 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1213] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1213] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1216 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1213] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1212 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2428 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2429 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1270(Jurisdiction934): - pass - - -class Disclosure1217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1270] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1220 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1214] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1214] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1217 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1214] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1213 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure46(RequiredDisclosure): - pass - - -class Compliance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure46] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets61317(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset46] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance46 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets61318(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping49] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2430 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2431 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1272(Jurisdiction934): - pass - - -class Disclosure1218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1272] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1221 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1215] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1215] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1218 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1215] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization46 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1214 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2432 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2433 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1273(Jurisdiction934): - pass - - -class Disclosure1219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1273] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1222 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1216] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1216] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1219 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1216] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1215 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2434 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2435 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1274(Jurisdiction934): - pass - - -class Disclosure1220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1274] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1223 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1217] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1217] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1220 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1217] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1216 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2436 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2437 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1275(Jurisdiction934): - pass - - -class Disclosure1221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1275] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1224 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1218] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1218] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1221 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1218] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1217 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2438 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2439 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1276(Jurisdiction934): - pass - - -class Disclosure1222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1276] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1225 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1219] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1219] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1222 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1219] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media90 | Media91, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl45 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1218 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2440 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2441 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1277(Jurisdiction934): - pass - - -class Disclosure1223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1277] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1226 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1220] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1220] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1223 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1220] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method62 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method62.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1219 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2442 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2443 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1278(Jurisdiction934): - pass - - -class Disclosure1224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1278] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1227 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1221] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1221] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1224 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1221] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target104 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target104.linear - provenance: Annotated[ - Provenance1220 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2444 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2445 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1279(Jurisdiction934): - pass - - -class Disclosure1225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1279] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1228 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1222] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1222] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1225 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1222] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target105 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target105.linear - provenance: Annotated[ - Provenance1221 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets6131( - RootModel[ - Assets6132 - | Assets6133 - | Assets6134 - | Assets6135 - | Assets6136 - | Assets6137 - | Assets6138 - | Assets6139 - | Assets61310 - | Assets61311 - | Assets61312 - | Assets61313 - | Assets61314 - | Assets61315 - | Assets490 - | Assets61317 - | Assets61318 - | Assets61319 - | Assets61320 - | Assets61321 - | Assets61322 - | Assets61323 - ] -): - root: Annotated[ - Assets6132 - | Assets6133 - | Assets6134 - | Assets6135 - | Assets6136 - | Assets6137 - | Assets6138 - | Assets6139 - | Assets61310 - | Assets61311 - | Assets61312 - | Assets61313 - | Assets61314 - | Assets61315 - | Assets490 - | Assets61317 - | Assets61318 - | Assets61319 - | Assets61320 - | Assets61321 - | Assets61322 - | Assets61323, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets613(RootModel[list[Assets6131]]): - root: Annotated[list[Assets6131], Field(min_length=1)] - - -class EmbeddedProvenanceItem1223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2446 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2447 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1280(Jurisdiction934): - pass - - -class Disclosure1226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1280] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1229 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1223] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1223] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1226 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1223] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1222 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo152 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand55(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride151 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right25(Right20): - pass - - -class EmbeddedProvenanceItem1224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2448 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2449 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1281(Jurisdiction934): - pass - - -class Disclosure1227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1281] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1230 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1224] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1224] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1227 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1224] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs | FormatOptionRefs8 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets591 - | Assets592 - | Assets593 - | Assets594 - | Assets595 - | Assets596 - | Assets597 - | Assets598 - | Assets599 - | Assets600 - | Assets601 - | Assets602 - | Assets603 - | Assets604 - | Assets605 - | Assets606 - | Assets607 - | Assets608 - | Assets609 - | Assets610 - | Assets611 - | Assets612 - | Assets613, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand55 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right25] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1223 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Variant(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - build_variant_id: Annotated[ - str, - Field( - description='Build-time handle for this produced variant — the leaf-level lineage anchor for the creative. Minted per produced variant. Its OWN namespace — MUST NOT reuse a `preview_id` (preview renders) or a served `variant_id` (delivery). Refinement parentage and build-time QA re-rolls anchor on this leaf id, NOT on the call-level `build_creative_id`. On the canonical promotion path, this value becomes the durable `creative_id` when the leaf is trafficked / added to the library; delivery outcomes and `report_usage` then join by `creative_id`. An untrafficked leaf has no `report_usage` key — it is billed via the inline per-leaf `vendor_cost` only, and `report_usage` reconciliation applies once the leaf earns a `creative_id`.' - ), - ] - recipe_hash: Annotated[ - str | None, - Field( - description='Optional agent-computed, opaque, agent-scoped identity for the build-determining inputs that produced this variant leaf. Stable for identical inputs as defined by the agent, and comparable only within the same agent. Multiple leaves from the same best-of-N recipe SHOULD carry the same value so clients can group alternative outputs by their shared source recipe. ETag-style semantics: the protocol defines the field and contract, not the hash algorithm or canonical input set. Identifies generative-input identity, not output equality, legal/disclosure equivalence, or the build-to-delivery join.' - ), - ] = None - parent_build_variant_id: Annotated[ - str | None, - Field( - description="When this variant was produced by refining a prior build (request `refine_from_build_variant_id`), the source leaf's `build_variant_id` — establishing refinement lineage (a leaf may itself be refined, forming a chain). Absent for first-generation builds. AI-derivative attribution rides the manifest's existing `provenance`; this field carries only the lineage edge." - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest15 | CreativeManifest16, - Field( - description='The generated creative for this variant. Carries its own format_id.', - title='Creative Manifest', - ), - ] - variant_axis_value: Annotated[ - Any | None, - Field( - description='The value of the variant axis that produced this variant (e.g. the voice id, theme name, or config value). Lets the buyer correlate the variant to its A/B cell.' - ), - ] = None - recommended: Annotated[ - bool | None, - Field( - description="Set when keep_mode was keep_one/keep_some — flags the agent's recommended pick(s). Advisory." - ), - ] = None - rank: Annotated[ - int | None, - Field( - description="Agent's ranking of this variant (1 = best) when it scored alternatives (best-of-N). Advisory.", - ge=1, - ), - ] = None - eval: Annotated[ - Eval | None, - Field( - description="Optional per-leaf evaluation block populated when the request supplied an `evaluator` and the agent advertises creative.supports_evaluator. Experimental (x-status: experimental) — part of the evaluator surface; sellers populating it MUST list `creative.evaluator` in experimental_features. The rank-side of the get_creative_features feature oracle: it carries the creative-feature values this leaf was scored on, which is what the gate-then-rank pipeline (evaluator.feature_requirement[] gate → evaluator.rank_by ordering) and `recommended`/`rank` are computed over. `eval.features[]` is `creative/creative-feature-result.json[]` — the same shape get_creative_features.results[] returns; the wrapper is open (additionalProperties:true) while each feature item is closed. A leaf's eval is a feature measurement, not a pass/fail verdict (a verdict is a categorical string feature value gated via feature_requirement.allowed_values). Leaves the agent dropped via the gate are not present; this block appears only on returned (recommended/billed) leaves. Advisory; does not change what is produced or billed." - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate-card pricing option was applied for THIS variant leaf. Pass in report_usage after promotion.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Cost incurred for this variant leaf, denominated in currency. REQUIRED on every produced leaf whenever the build reports cost (the top-level aggregate `vendor_cost` is present) — leaves are the billing source of truth, and an untrafficked leaf is reconciled from this field alone (it never earns a `creative_id` / `report_usage` entry). A CPM-deferred leaf reports 0 here (a value, not an omission).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for vendor_cost. Co-required with vendor_cost.', - pattern='^[A-Z]{3}$', - ), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption for this variant leaf. Informational; vendor_cost is the billing source of truth.', - title='Creative Consumption', - ), - ] = None - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - build_creative_id: Annotated[ - str | None, - Field( - description='Build-time handle for this produced creative within this response. Distinct from a library creative_id — a build_creative_id is not yet persisted/servable; it acquires a creative_id when a chosen variant is trafficked or added to the library (lazy promotion).' - ), - ] = None - catalog_item_ref: Annotated[ - CatalogItemRef | None, - Field( - description='When this creative was produced by fanning out over a catalog, identifies the source item.' - ), - ] = None - signal_condition: Annotated[ - SignalCondition | SignalCondition12 | SignalCondition13 | None, - Field( - description='When this creative group was produced by fanning out over signal_conditions, the SignalTargeting condition this group is FOR (e.g. weather=rain). Sibling to catalog_item_ref. Carries the SAME signal_ref identity the sales-side package targeting uses, so a sales agent can structurally match condition identity and reject-at-trafficking (SIGNAL_TARGETING_INCOMPATIBLE) when a creative is assigned to an incompatible package.', - discriminator='value_type', - title='Signal Targeting', - ), - ] = None - variants: Annotated[ - list[Variant] | None, - Field( - description='Choose-among alternatives produced for this creative group (voices, themes, best-of-N, etc.). At least one. Each is an independently-tagged, independently-billed build.', - min_length=1, - ), - ] = None - errors: Annotated[ - list[Error54] | None, - Field( - description='Per-creative errors when this catalog item failed to build. Present only on failed items; does not fail the batch (per-item non-atomic). A failed entry carries errors[] and no variants[]; a successful entry carries variants[] and SHOULD NOT carry errors.', - min_length=1, - ), - ] = None - - -class Result1196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError56 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig59 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creatives: Annotated[ - list[Creative], - Field( - description='One entry per produced creative group. With catalog fan-out, one entry per catalog item (bounded/sampled by max_creatives). This array is the produced group set; use variants[] inside each group for choose-among alternatives.', - min_length=1, - ), - ] - items_total: Annotated[ - int | None, - Field( - description='Total catalog items eligible for the build (before max_creatives sampling). Lets the buyer see that creatives[] is a sample of a larger set.', - ge=0, - ), - ] = None - items_returned: Annotated[ - int | None, - Field( - description='Number of creatives returned in creatives[] (after max_creatives sampling).', - ge=0, - ), - ] = None - leaves_total: Annotated[ - int | None, - Field( - description='Total leaves the request would have produced (≈ items_to_produce × variants_per_item, × conditions_total when signal_conditions was sent). Present when a max_spend cap may have stopped production short. Counts LEAVES, not catalog items — so it expresses a shortfall even for a variant-only fan-out with no catalog.', - ge=0, - ), - ] = None - leaves_returned: Annotated[ - int | None, - Field( - description="Number of leaves actually produced and billed across creatives[].variants[]. When budget_status is 'capped', leaves_returned < leaves_total is the leaf-granular shortfall signal (items_returned/items_total are catalog-item counts and do not capture a mid-item or variant-only cap).", - ge=0, - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Aggregate cost across all variant leaves, denominated in currency. MUST equal the sum of the per-leaf vendor_cost values (leaves are the source of truth). When present, every produced leaf MUST carry its own vendor_cost + currency (enforced) so the sum invariant is checkable; omit this aggregate only for a genuinely free build.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for the aggregate vendor_cost.', - pattern='^[A-Z]{3}$', - ), - ] = None - keep_mode_applied: Annotated[ - KeepModeApplied | None, - Field( - description="Echoes the `keep_mode` the agent applied (mirrors the request hint). Present when the request set `keep_mode`. `keep_mode` is advisory — it does not change what is produced or billed (you pay for every leaf in `variants[]`) — so this echo is the buyer's confirmation that the hint was received, the audit paper trail for a 'I asked for keep_one but was billed for N' dispute. Whether the agent acted on it shows in the `recommended`/`rank` it set on the leaves." - ), - ] = None - selection_strategy_applied: Annotated[ - SelectionStrategyApplied | None, - Field( - description='Echoes the selection_strategy the agent applied when max_creatives sampling occurred (mirrors keep_mode_applied). Present when the request set selection_strategy. The ranking itself shows in the rank/recommended on creatives[].variants[].', - title='Creative Selection Strategy', - ), - ] = None - budget_status: Annotated[ - BudgetStatus | None, - Field( - description='`complete` (default; absent == complete for back-compat) means the agent produced everything requested. `capped` means a `max_spend` ceiling stopped production early: every returned leaf is real/billed, and an advisory `BUDGET_CAP_REACHED` entry in `errors[]` is the authoritative cap signal. The leaf-granular shortfall is `leaves_returned` < `leaves_total` (do NOT rely on items_returned < items_total — that is also the normal max_creatives-sampling signal and does not capture a mid-item or variant-only cap). This is a successful partial build, not a failure.' - ), - ] = BudgetStatus.complete - errors: Annotated[ - list[Error55] | None, - Field( - description='Advisory (non-terminal) entries on an otherwise-successful build — e.g. a `BUDGET_CAP_REACHED` notice when `budget_status` is `capped`. Terminal failures use the BuildCreativeError shape, not this field.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the earliest generated asset URL expires across all variants. Re-build after this time to get fresh URLs.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig60(PushNotificationConfig52): - pass - - -class Result1289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError57 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig60 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - mode: Annotated[ - Literal['estimate'], - Field( - description='Echoes the request mode; discriminates this dry-run envelope from the producing shapes.' - ), - ] = 'estimate' - estimate: Annotated[ - Estimate, Field(description='Projected cost for what mode:"execute" would have produced.') - ] - expires_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp after which this estimate's inputs/prices may no longer hold." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig61(PushNotificationConfig52): - pass - - -class Result1290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError58 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig61 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error56], - Field( - description='Array of errors explaining why creative generation failed.\n\n**Per-format attribution on multi-format requests.** When the request used `target_format_ids[]` (a batch request), sellers MUST attribute each failure to the specific format(s) that caused it so buyers can correct and retry the failing subset rather than re-running the entire batch. Two attribution surfaces, populated together:\n\n1. **`error.field`** — set to `target_format_ids[N]` where `N` is the zero-based index of the failing format in the request\'s `target_format_ids[]` array (e.g., `target_format_ids[1]` for the second requested format). Mirrors the JSONPath-lite convention `error.field` uses elsewhere. Required when the error is format-scoped.\n2. **`error.details.format_id`** — the resolved `format_id` value (e.g., `"meta-reels-9x16"`). Required when the error is format-scoped. Lets buyers dispatch on the format identity without re-parsing `error.field`.\n\nErrors not attributable to a specific format (whole-batch failures: authentication, governance denial, transport-level errors) MAY omit `field` and `details.format_id`. Buyers MUST treat per-format errors as scoped to the named format only — a `correctable` error on `target_format_ids[1]` does NOT mean the buyer must reshape the entire batch; they may retry just that format with corrected input. Sellers SHOULD emit one error per failing format (rather than collapsing multiple format failures into a single error entry) so per-format recovery routing is unambiguous.', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig62(PushNotificationConfig52): - pass - - -class Result1291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError59 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig62 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error57] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig63(PushNotificationConfig52): - pass - - -class EmbeddedProvenanceItem1225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2450 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2451 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1282(Jurisdiction934): - pass - - -class Disclosure1228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1282] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1231 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1225] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1225] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1228 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1225] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1224 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo153 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand56(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride152 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class ReportingBucket5(ReportingBucket): - pass - - -class Authentication72(Authentication59): - pass - - -class NotificationConfig5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication72 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: AccountStatus - brand: Annotated[ - Brand56 | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: BillingParty | None = None - billing_entity: Annotated[ - BillingEntity4 | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: PaymentTerms | None = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: AccountScope | None = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket5 | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig5] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creative9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Creative ID from the request')] - account: Annotated[ - Account53 | None, - Field( - description='Account that owns this creative', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - action: Annotated[ - Action, - Field( - description='Action taken for this creative during this sync operation (lifecycle operation, not approval state).', - title='Creative Action', - ), - ] - status: Annotated[ - Status327 | None, - Field( - description='Advisory review-lifecycle state of the creative after this sync — a UI hint and polling-scheduling signal, NOT a spend-authorization gate. Orthogonal to action — action says what the sync did (created, updated, ...); status says where the creative sits in review. Values come from CreativeStatus only (processing, pending_review, approved, suspended, rejected, archived) — never from CreativeAction. Sellers with async review return processing or pending_review; sellers with synchronous review MAY return a terminal value (approved, rejected) or suspended when a recoverable dependency/authorization gate prevents serving. Buyers MUST NOT gate downstream spend or package activation on status: approved from this response — a compromised or buggy seller could declare approved while bypassing content-policy review. Reconcile via list_creatives or a signed review webhook before committing spend. Authoritative state is always via list_creatives. MUST be omitted when action is failed or deleted (the creative has no meaningful review state — failure details belong in the errors array; deleted creatives are gone from the library). Omit entirely when the seller has no review lifecycle at all.', - title='Creative Status', - ), - ] = None - platform_id: Annotated[ - str | None, Field(description='Platform-specific ID assigned to the creative') - ] = None - changes: Annotated[ - list[str] | None, - Field(description="Field names that were modified (only present when action='updated')"), - ] = None - errors: Annotated[ - list[Error60] | None, - Field(description="Validation or processing errors (only present when action='failed')"), - ] = None - warnings: Annotated[ - list[str] | None, Field(description='Non-fatal warnings about this creative') - ] = None - preview_url: Annotated[ - AnyUrl | None, - Field( - description='Preview URL for generative creatives (only present for generative formats)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when preview link expires (only present when preview_url exists)' - ), - ] = None - assigned_to: Annotated[ - list[str] | None, - Field( - description='Package IDs this creative was successfully assigned to (only present when assignments were requested)' - ), - ] = None - assignment_errors: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], str] | None, - Field( - description='Assignment errors by package ID (only present when assignment failures occurred)' - ), - ] = None - - -class Result1295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError60 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig63 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - dry_run: Annotated[ - bool | None, Field(description='Whether this was a dry run (no actual changes made)') - ] = None - creatives: Annotated[ - list[Creative9], - Field( - description="Results for each creative processed. Items with action='failed' indicate per-item validation/processing failures, not operation-level failures." - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Authentication73(Authentication): - pass - - -class PushNotificationConfig64(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication73 | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Result1297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError61 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig64 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error61], - Field( - description='Operation-level errors that prevented processing any creatives (e.g., authentication failure, service unavailable, invalid request format)', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig65(PushNotificationConfig64): - pass - - -class Result1298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError62 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig65 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error62] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig66(PushNotificationConfig64): - pass - - -class Result1302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError63 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig66 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - dry_run: Annotated[ - bool | None, Field(description='Whether this was a dry run (no actual changes made)') - ] = None - catalogs: Annotated[ - list[Catalog], - Field( - description="Results for each catalog processed. Items with action='failed' indicate per-catalog validation/processing failures, not operation-level failures." - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig67(PushNotificationConfig64): - pass - - -class Result1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError64 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig67 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error65], - Field( - description='Operation-level errors that prevented processing any catalogs (e.g., authentication failure, service unavailable, invalid request format)', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig68(PushNotificationConfig64): - pass - - -class Result1304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError65 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig68 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error66] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TasksGetResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - task_type: Annotated[TaskType, Field(description='Type of AdCP operation', title='Task Type')] - protocol: AdCPProtocol - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Whether this task has webhook configuration') - ] = None - progress: Annotated[ - Progress | None, Field(description='Progress information for long-running tasks') - ] = None - error: Annotated[Error41 | None, Field(description='Error details for failed tasks')] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Complete conversation history for this task (only included if include_history was true in request)' - ), - ] = None - result: Annotated[ - Result931 - | Result956 - | Result957 - | Result978 - | Result979 - | Result982 - | Result983 - | Result984 - | Result990 - | Result991 - | Result992 - | Result993 - | Result994 - | Result995 - | Result1000 - | Result1001 - | Result1002 - | Result1003 - | Result1004 - | Result1005 - | Result1010 - | Result1103 - | Result1196 - | Result1289 - | Result1290 - | Result1291 - | Result1292 - | Result1293 - | Result1294 - | Result1295 - | Result1297 - | Result1298 - | Result1299 - | Result1300 - | Result1301 - | Result1302 - | Result1303 - | Result1304 - | Result1305 - | Result1306 - | Result1307 - | None, - Field( - description="Task-specific completion payload. Present when status is 'completed' and include_result was true in the request; absent otherwise. For failed tasks, use the error field instead. Uses the same anyOf union as the push-notification webhook result field.", - title='AdCP Async Response Data', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/core/tasks_list_request.py b/src/adcp/types/generated_poc/bundled/core/tasks_list_request.py deleted file mode 100644 index fabd4174..00000000 --- a/src/adcp/types/generated_poc/bundled/core/tasks_list_request.py +++ /dev/null @@ -1,765 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/core/tasks_list_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent2453(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2453 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account55(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Field1(StrEnum): - created_at = 'created_at' - updated_at = 'updated_at' - status = 'status' - task_type = 'task_type' - protocol = 'protocol' - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at - direction: Annotated[ - Direction | None, Field(description='Sort direction', title='Sort Direction') - ] = Direction.desc - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class AdCPProtocol(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - governance = 'governance' - creative = 'creative' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - measurement = 'measurement' - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: AdCPProtocol | None = None - protocols: Annotated[ - list[AdCPProtocol] | None, - Field(description='Filter by multiple AdCP protocols', min_length=1), - ] = None - status: TaskStatus | None = None - statuses: Annotated[ - list[TaskStatus] | None, Field(description='Filter by multiple task statuses', min_length=1) - ] = None - task_type: TaskType | None = None - task_types: Annotated[ - list[TaskType] | None, Field(description='Filter by multiple task types', min_length=1) - ] = None - created_after: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') - ] = None - created_before: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated before this date (ISO 8601)'), - ] = None - task_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific task IDs', max_length=100, min_length=1), - ] = None - context_contains: Annotated[ - str | None, - Field( - description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Filter tasks that have webhook configuration when true') - ] = None - - -class TasksListRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account55 | None, - Field( - description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( - None - ) - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - include_history: Annotated[ - bool | None, - Field( - description='Include full conversation history for each task (may significantly increase response size)' - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py b/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py deleted file mode 100644 index 08d34993..00000000 --- a/src/adcp/types/generated_poc/bundled/core/tasks_list_response.py +++ /dev/null @@ -1,451 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/core/tasks_list_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class DomainBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy: Annotated[ - int | None, - Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), - ] = None - signals: Annotated[ - int | None, Field(description='Number of signals tasks in results', ge=0) - ] = None - creative: Annotated[ - int | None, Field(description='Number of creative tasks in results', ge=0) - ] = None - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class SortApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: str - direction: Direction - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - int | None, - Field(description='Total number of tasks matching filters (across all pages)', ge=0), - ] = None - returned: Annotated[ - int | None, Field(description='Number of tasks returned in this response', ge=0) - ] = None - domain_breakdown: Annotated[ - DomainBreakdown | None, Field(description='Count of tasks by domain') - ] = None - status_breakdown: Annotated[ - dict[str, int] | None, Field(description='Count of tasks by status') - ] = None - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Domain(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - creative = 'creative' - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class Task(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[TaskType, Field(description='Type of AdCP operation', title='Task Type')] - domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] - status: TaskStatus - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Whether this task has webhook configuration') - ] = None - - -class TasksListResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] - pagination: Annotated[ - Pagination, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/__init__.py b/src/adcp/types/generated_poc/bundled/creative/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_request.py b/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_request.py deleted file mode 100644 index af8063e8..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_request.py +++ /dev/null @@ -1,672 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/get_creative_delivery_request.json -# timestamp: 2026-05-28T10:34:10+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent43(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetCreativeDeliveryRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account13 | None, - Field( - description='Account for routing and scoping. Limits results to creatives within this account.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific media buys by publisher ID. If omitted, returns creative delivery across all matching media buys.', - min_length=1, - ), - ] = None - creative_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific creatives by ID. If omitted, returns delivery for all creatives matching the other filters.', - min_length=1, - ), - ] = None - start_date: Annotated[ - str | None, - Field( - description="Start date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - end_date: Annotated[ - str | None, - Field( - description="End date for delivery period (YYYY-MM-DD). Interpreted in the platform's reporting timezone.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - max_variants: Annotated[ - int | None, - Field( - description='Maximum number of variants to return per creative. When omitted, the agent returns all variants. Use this to limit response size for generative creatives that may produce large numbers of variants.', - ge=1, - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters for the creatives array in the response. Uses cursor-based pagination consistent with other list operations.', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_response.py b/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_response.py deleted file mode 100644 index e9d1dc89..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/get_creative_delivery_response.py +++ /dev/null @@ -1,23381 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/get_creative_delivery_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[AwareDatetime, Field(description='ISO 8601 start timestamp')] - end: Annotated[AwareDatetime, Field(description='ISO 8601 end timestamp')] - timezone: Annotated[ - str | None, - Field( - description="IANA timezone identifier for the reporting period (e.g., 'America/New_York', 'UTC'). Platforms report in their native timezone." - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Kind(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Period(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class QuartileData(AdCPBaseModel): - q1_views: Annotated[float | None, Field(description='25% completion views', ge=0.0)] = None - q2_views: Annotated[float | None, Field(description='50% completion views', ge=0.0)] = None - q3_views: Annotated[float | None, Field(description='75% completion views', ge=0.0)] = None - q4_views: Annotated[float | None, Field(description='100% completion views', ge=0.0)] = None - - -class VenueBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - venue_id: Annotated[str, Field(description='Venue identifier')] - venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None - venue_type: Annotated[ - str | None, - Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), - ] = None - impressions: Annotated[int, Field(description='Impressions delivered at this venue', ge=0)] - loop_plays: Annotated[int | None, Field(description='Loop plays at this venue', ge=0)] = None - screens_used: Annotated[ - int | None, Field(description='Number of screens used at this venue', ge=0) - ] = None - - -class DoohMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - loop_plays: Annotated[ - int | None, Field(description='Number of times ad played in rotation', ge=0) - ] = None - screens_used: Annotated[ - int | None, Field(description='Number of unique screens displaying the ad', ge=0) - ] = None - screen_time_seconds: Annotated[ - int | None, Field(description='Total display time in seconds', ge=0) - ] = None - sov_achieved: Annotated[ - float | None, - Field(description='Actual share of voice delivered (0.0 to 1.0)', ge=0.0, le=1.0), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." - ), - ] = None - venue_breakdown: Annotated[ - list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent45(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent45): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class Period2(Period): - pass - - -class ReachWindow3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period2 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics2(DoohMetrics): - pass - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent45): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent45): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class DeclaredBy26(DeclaredBy): - pass - - -class VerifyAgent52(VerifyAgent): - pass - - -class VerifyAgent53(VerifyAgent45): - pass - - -class VerificationItem26(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy27(DeclaredBy): - pass - - -class VerifyAgent54(VerifyAgent): - pass - - -class VerifyAgent55(VerifyAgent45): - pass - - -class VerificationItem27(VerificationItem): - pass - - -class DeclaredBy28(DeclaredBy): - pass - - -class VerifyAgent56(VerifyAgent): - pass - - -class VerifyAgent57(VerifyAgent45): - pass - - -class VerificationItem28(VerificationItem): - pass - - -class DeclaredBy29(DeclaredBy): - pass - - -class VerifyAgent58(VerifyAgent): - pass - - -class VerifyAgent59(VerifyAgent45): - pass - - -class VerificationItem29(VerificationItem): - pass - - -class DeclaredBy30(DeclaredBy): - pass - - -class VerifyAgent60(VerifyAgent): - pass - - -class VerifyAgent61(VerifyAgent45): - pass - - -class VerificationItem30(VerificationItem): - pass - - -class DeclaredBy31(DeclaredBy): - pass - - -class VerifyAgent62(VerifyAgent): - pass - - -class VerifyAgent63(VerifyAgent45): - pass - - -class VerificationItem31(VerificationItem): - pass - - -class DeclaredBy32(DeclaredBy): - pass - - -class VerifyAgent64(VerifyAgent): - pass - - -class VerifyAgent65(VerifyAgent45): - pass - - -class VerificationItem32(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy33(DeclaredBy): - pass - - -class VerifyAgent66(VerifyAgent): - pass - - -class VerifyAgent67(VerifyAgent45): - pass - - -class VerificationItem33(VerificationItem): - pass - - -class DeclaredBy34(DeclaredBy): - pass - - -class VerifyAgent68(VerifyAgent): - pass - - -class VerifyAgent69(VerifyAgent45): - pass - - -class VerificationItem34(VerificationItem): - pass - - -class DeclaredBy35(DeclaredBy): - pass - - -class VerifyAgent70(VerifyAgent): - pass - - -class VerifyAgent71(VerifyAgent45): - pass - - -class VerificationItem35(VerificationItem): - pass - - -class DeclaredBy36(DeclaredBy): - pass - - -class VerifyAgent72(VerifyAgent): - pass - - -class VerifyAgent73(VerifyAgent45): - pass - - -class VerificationItem36(VerificationItem): - pass - - -class DeclaredBy37(DeclaredBy): - pass - - -class VerifyAgent74(VerifyAgent): - pass - - -class VerifyAgent75(VerifyAgent45): - pass - - -class VerificationItem37(VerificationItem): - pass - - -class DeclaredBy38(DeclaredBy): - pass - - -class VerifyAgent76(VerifyAgent): - pass - - -class VerifyAgent77(VerifyAgent45): - pass - - -class VerificationItem38(VerificationItem): - pass - - -class DeclaredBy39(DeclaredBy): - pass - - -class VerifyAgent78(VerifyAgent): - pass - - -class VerifyAgent79(VerifyAgent45): - pass - - -class VerificationItem39(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role45(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role45, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status24(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status24, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy40(DeclaredBy): - pass - - -class VerifyAgent80(VerifyAgent): - pass - - -class VerifyAgent81(VerifyAgent45): - pass - - -class VerificationItem40(VerificationItem): - pass - - -class DeclaredBy41(DeclaredBy): - pass - - -class VerifyAgent82(VerifyAgent): - pass - - -class VerifyAgent83(VerifyAgent45): - pass - - -class VerificationItem41(VerificationItem): - pass - - -class DeclaredBy42(DeclaredBy): - pass - - -class VerifyAgent84(VerifyAgent): - pass - - -class VerifyAgent85(VerifyAgent45): - pass - - -class VerificationItem42(VerificationItem): - pass - - -class DeclaredBy43(DeclaredBy): - pass - - -class VerifyAgent86(VerifyAgent): - pass - - -class VerifyAgent87(VerifyAgent45): - pass - - -class VerificationItem43(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy44(DeclaredBy): - pass - - -class VerifyAgent88(VerifyAgent): - pass - - -class VerifyAgent89(VerifyAgent45): - pass - - -class VerificationItem44(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy45(DeclaredBy): - pass - - -class VerifyAgent90(VerifyAgent): - pass - - -class VerifyAgent91(VerifyAgent45): - pass - - -class VerificationItem45(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy46(DeclaredBy): - pass - - -class VerifyAgent92(VerifyAgent): - pass - - -class VerifyAgent93(VerifyAgent45): - pass - - -class VerificationItem46(VerificationItem): - pass - - -class Target2(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy47(DeclaredBy): - pass - - -class VerifyAgent94(VerifyAgent): - pass - - -class VerifyAgent95(VerifyAgent45): - pass - - -class VerificationItem47(VerificationItem): - pass - - -class DeclaredBy48(DeclaredBy): - pass - - -class VerifyAgent96(VerifyAgent): - pass - - -class VerifyAgent97(VerifyAgent45): - pass - - -class VerificationItem48(VerificationItem): - pass - - -class DeclaredBy49(DeclaredBy): - pass - - -class VerifyAgent98(VerifyAgent): - pass - - -class VerifyAgent99(VerifyAgent45): - pass - - -class VerificationItem49(VerificationItem): - pass - - -class DeclaredBy50(DeclaredBy): - pass - - -class VerifyAgent100(VerifyAgent): - pass - - -class VerifyAgent101(VerifyAgent45): - pass - - -class VerificationItem50(VerificationItem): - pass - - -class DeclaredBy51(DeclaredBy): - pass - - -class VerifyAgent102(VerifyAgent): - pass - - -class VerifyAgent103(VerifyAgent45): - pass - - -class VerificationItem51(VerificationItem): - pass - - -class DeclaredBy52(DeclaredBy): - pass - - -class VerifyAgent104(VerifyAgent): - pass - - -class VerifyAgent105(VerifyAgent45): - pass - - -class VerificationItem52(VerificationItem): - pass - - -class DeclaredBy53(DeclaredBy): - pass - - -class VerifyAgent106(VerifyAgent): - pass - - -class VerifyAgent107(VerifyAgent45): - pass - - -class VerificationItem53(VerificationItem): - pass - - -class DeclaredBy54(DeclaredBy): - pass - - -class VerifyAgent108(VerifyAgent): - pass - - -class VerifyAgent109(VerifyAgent45): - pass - - -class VerificationItem54(VerificationItem): - pass - - -class DeclaredBy55(DeclaredBy): - pass - - -class VerifyAgent110(VerifyAgent): - pass - - -class VerifyAgent111(VerifyAgent45): - pass - - -class VerificationItem55(VerificationItem): - pass - - -class DeclaredBy56(DeclaredBy): - pass - - -class VerifyAgent112(VerifyAgent): - pass - - -class VerifyAgent113(VerifyAgent45): - pass - - -class VerificationItem56(VerificationItem): - pass - - -class DeclaredBy57(DeclaredBy): - pass - - -class VerifyAgent114(VerifyAgent): - pass - - -class VerifyAgent115(VerifyAgent45): - pass - - -class VerificationItem57(VerificationItem): - pass - - -class DeclaredBy58(DeclaredBy): - pass - - -class VerifyAgent116(VerifyAgent): - pass - - -class VerifyAgent117(VerifyAgent45): - pass - - -class VerificationItem58(VerificationItem): - pass - - -class DeclaredBy59(DeclaredBy): - pass - - -class VerifyAgent118(VerifyAgent): - pass - - -class VerifyAgent119(VerifyAgent45): - pass - - -class VerificationItem59(VerificationItem): - pass - - -class DeclaredBy60(DeclaredBy): - pass - - -class VerifyAgent120(VerifyAgent): - pass - - -class VerifyAgent121(VerifyAgent45): - pass - - -class VerificationItem60(VerificationItem): - pass - - -class DeclaredBy61(DeclaredBy): - pass - - -class VerifyAgent122(VerifyAgent): - pass - - -class VerifyAgent123(VerifyAgent45): - pass - - -class VerificationItem61(VerificationItem): - pass - - -class ReferenceAsset2(ReferenceAsset): - pass - - -class FeedFieldMapping1(FeedFieldMapping): - pass - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class DeclaredBy62(DeclaredBy): - pass - - -class VerifyAgent124(VerifyAgent): - pass - - -class VerifyAgent125(VerifyAgent45): - pass - - -class VerificationItem62(VerificationItem): - pass - - -class DeclaredBy63(DeclaredBy): - pass - - -class VerifyAgent126(VerifyAgent): - pass - - -class VerifyAgent127(VerifyAgent45): - pass - - -class VerificationItem63(VerificationItem): - pass - - -class DeclaredBy64(DeclaredBy): - pass - - -class VerifyAgent128(VerifyAgent): - pass - - -class VerifyAgent129(VerifyAgent45): - pass - - -class VerificationItem64(VerificationItem): - pass - - -class DeclaredBy65(DeclaredBy): - pass - - -class VerifyAgent130(VerifyAgent): - pass - - -class VerifyAgent131(VerifyAgent45): - pass - - -class VerificationItem65(VerificationItem): - pass - - -class DeclaredBy66(DeclaredBy): - pass - - -class VerifyAgent132(VerifyAgent): - pass - - -class VerifyAgent133(VerifyAgent45): - pass - - -class VerificationItem66(VerificationItem): - pass - - -class DeclaredBy67(DeclaredBy): - pass - - -class VerifyAgent134(VerifyAgent): - pass - - -class VerifyAgent135(VerifyAgent45): - pass - - -class VerificationItem67(VerificationItem): - pass - - -class DeclaredBy68(DeclaredBy): - pass - - -class VerifyAgent136(VerifyAgent): - pass - - -class VerifyAgent137(VerifyAgent45): - pass - - -class VerificationItem68(VerificationItem): - pass - - -class DeclaredBy69(DeclaredBy): - pass - - -class VerifyAgent138(VerifyAgent): - pass - - -class VerifyAgent139(VerifyAgent45): - pass - - -class VerificationItem69(VerificationItem): - pass - - -class DeclaredBy70(DeclaredBy): - pass - - -class VerifyAgent140(VerifyAgent): - pass - - -class VerifyAgent141(VerifyAgent45): - pass - - -class VerificationItem70(VerificationItem): - pass - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Use(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ExcludedCountry(Country): - pass - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class Right(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[Use], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: Annotated[ - RightType | None, - Field( - description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.', - title='Right Type', - ), - ] = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Type(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy71(DeclaredBy): - pass - - -class VerifyAgent142(VerifyAgent): - pass - - -class VerifyAgent143(VerifyAgent45): - pass - - -class VerificationItem71(VerificationItem): - pass - - - -class DeclaredBy72(DeclaredBy): - pass - - -class VerifyAgent144(VerifyAgent): - pass - - -class VerifyAgent145(VerifyAgent45): - pass - - -class VerificationItem72(VerificationItem): - pass - - -class DeclaredBy73(DeclaredBy): - pass - - -class VerifyAgent146(VerifyAgent): - pass - - -class VerifyAgent147(VerifyAgent45): - pass - - -class VerificationItem73(VerificationItem): - pass - - -class DeclaredBy74(DeclaredBy): - pass - - -class VerifyAgent148(VerifyAgent): - pass - - -class VerifyAgent149(VerifyAgent45): - pass - - -class VerificationItem74(VerificationItem): - pass - - -class DeclaredBy75(DeclaredBy): - pass - - -class VerifyAgent150(VerifyAgent): - pass - - -class VerifyAgent151(VerifyAgent45): - pass - - -class VerificationItem75(VerificationItem): - pass - - -class DeclaredBy76(DeclaredBy): - pass - - -class VerifyAgent152(VerifyAgent): - pass - - -class VerifyAgent153(VerifyAgent45): - pass - - -class VerificationItem76(VerificationItem): - pass - - -class DeclaredBy77(DeclaredBy): - pass - - -class VerifyAgent154(VerifyAgent): - pass - - -class VerifyAgent155(VerifyAgent45): - pass - - -class VerificationItem77(VerificationItem): - pass - - -class DeclaredBy78(DeclaredBy): - pass - - -class VerifyAgent156(VerifyAgent): - pass - - -class VerifyAgent157(VerifyAgent45): - pass - - -class VerificationItem78(VerificationItem): - pass - - -class DeclaredBy79(DeclaredBy): - pass - - -class VerifyAgent158(VerifyAgent): - pass - - -class VerifyAgent159(VerifyAgent45): - pass - - -class VerificationItem79(VerificationItem): - pass - - -class DeclaredBy80(DeclaredBy): - pass - - -class VerifyAgent160(VerifyAgent): - pass - - -class VerifyAgent161(VerifyAgent45): - pass - - -class VerificationItem80(VerificationItem): - pass - - -class DeclaredBy81(DeclaredBy): - pass - - -class VerifyAgent162(VerifyAgent): - pass - - -class VerifyAgent163(VerifyAgent45): - pass - - -class VerificationItem81(VerificationItem): - pass - - -class DeclaredBy82(DeclaredBy): - pass - - -class VerifyAgent164(VerifyAgent): - pass - - -class VerifyAgent165(VerifyAgent45): - pass - - -class VerificationItem82(VerificationItem): - pass - - -class DeclaredBy83(DeclaredBy): - pass - - -class VerifyAgent166(VerifyAgent): - pass - - -class VerifyAgent167(VerifyAgent45): - pass - - -class VerificationItem83(VerificationItem): - pass - - -class DeclaredBy84(DeclaredBy): - pass - - -class VerifyAgent168(VerifyAgent): - pass - - -class VerifyAgent169(VerifyAgent45): - pass - - -class VerificationItem84(VerificationItem): - pass - - -class DeclaredBy85(DeclaredBy): - pass - - -class VerifyAgent170(VerifyAgent): - pass - - -class VerifyAgent171(VerifyAgent45): - pass - - -class VerificationItem85(VerificationItem): - pass - - -class ReferenceAsset3(ReferenceAsset): - pass - - -class FeedFieldMapping2(FeedFieldMapping): - pass - - -class ReferenceAuthorization2(ReferenceAuthorization): - pass - - -class DeclaredBy86(DeclaredBy): - pass - - -class VerifyAgent172(VerifyAgent): - pass - - -class VerifyAgent173(VerifyAgent45): - pass - - -class VerificationItem86(VerificationItem): - pass - - -class DeclaredBy87(DeclaredBy): - pass - - -class VerifyAgent174(VerifyAgent): - pass - - -class VerifyAgent175(VerifyAgent45): - pass - - -class VerificationItem87(VerificationItem): - pass - - -class DeclaredBy88(DeclaredBy): - pass - - -class VerifyAgent176(VerifyAgent): - pass - - -class VerifyAgent177(VerifyAgent45): - pass - - -class VerificationItem88(VerificationItem): - pass - - -class DeclaredBy89(DeclaredBy): - pass - - -class VerifyAgent178(VerifyAgent): - pass - - -class VerifyAgent179(VerifyAgent45): - pass - - -class VerificationItem89(VerificationItem): - pass - - -class DeclaredBy90(DeclaredBy): - pass - - -class VerifyAgent180(VerifyAgent): - pass - - -class VerifyAgent181(VerifyAgent45): - pass - - -class VerificationItem90(VerificationItem): - pass - - -class DeclaredBy91(DeclaredBy): - pass - - -class VerifyAgent182(VerifyAgent): - pass - - -class VerifyAgent183(VerifyAgent45): - pass - - -class VerificationItem91(VerificationItem): - pass - - -class DeclaredBy92(DeclaredBy): - pass - - -class VerifyAgent184(VerifyAgent): - pass - - -class VerifyAgent185(VerifyAgent45): - pass - - -class VerificationItem92(VerificationItem): - pass - - -class DeclaredBy93(DeclaredBy): - pass - - -class VerifyAgent186(VerifyAgent): - pass - - -class VerifyAgent187(VerifyAgent45): - pass - - -class VerificationItem93(VerificationItem): - pass - - -class DeclaredBy94(DeclaredBy): - pass - - -class VerifyAgent188(VerifyAgent): - pass - - -class VerifyAgent189(VerifyAgent45): - pass - - -class VerificationItem94(VerificationItem): - pass - - -class DeclaredBy95(DeclaredBy): - pass - - -class VerifyAgent190(VerifyAgent): - pass - - -class VerifyAgent191(VerifyAgent45): - pass - - -class VerificationItem95(VerificationItem): - pass - - -class DeclaredBy96(DeclaredBy): - pass - - -class VerifyAgent192(VerifyAgent): - pass - - -class VerifyAgent193(VerifyAgent45): - pass - - -class VerificationItem96(VerificationItem): - pass - - -class DeclaredBy97(DeclaredBy): - pass - - -class VerifyAgent194(VerifyAgent): - pass - - -class VerifyAgent195(VerifyAgent45): - pass - - -class VerificationItem97(VerificationItem): - pass - - -class DeclaredBy98(DeclaredBy): - pass - - -class VerifyAgent196(VerifyAgent): - pass - - -class VerifyAgent197(VerifyAgent45): - pass - - -class VerificationItem98(VerificationItem): - pass - - -class DeclaredBy99(DeclaredBy): - pass - - -class VerifyAgent198(VerifyAgent): - pass - - -class VerifyAgent199(VerifyAgent45): - pass - - -class VerificationItem99(VerificationItem): - pass - - -class DeclaredBy100(DeclaredBy): - pass - - -class VerifyAgent200(VerifyAgent): - pass - - -class VerifyAgent201(VerifyAgent45): - pass - - -class VerificationItem100(VerificationItem): - pass - - -class DeclaredBy101(DeclaredBy): - pass - - -class VerifyAgent202(VerifyAgent): - pass - - -class VerifyAgent203(VerifyAgent45): - pass - - -class VerificationItem101(VerificationItem): - pass - - -class DeclaredBy102(DeclaredBy): - pass - - -class VerifyAgent204(VerifyAgent): - pass - - -class VerifyAgent205(VerifyAgent45): - pass - - -class VerificationItem102(VerificationItem): - pass - - -class DeclaredBy103(DeclaredBy): - pass - - -class VerifyAgent206(VerifyAgent): - pass - - -class VerifyAgent207(VerifyAgent45): - pass - - -class VerificationItem103(VerificationItem): - pass - - -class DeclaredBy104(DeclaredBy): - pass - - -class VerifyAgent208(VerifyAgent): - pass - - -class VerifyAgent209(VerifyAgent45): - pass - - -class VerificationItem104(VerificationItem): - pass - - -class DeclaredBy105(DeclaredBy): - pass - - -class VerifyAgent210(VerifyAgent): - pass - - -class VerifyAgent211(VerifyAgent45): - pass - - -class VerificationItem105(VerificationItem): - pass - - -class DeclaredBy106(DeclaredBy): - pass - - -class VerifyAgent212(VerifyAgent): - pass - - -class VerifyAgent213(VerifyAgent45): - pass - - -class VerificationItem106(VerificationItem): - pass - - -class DeclaredBy107(DeclaredBy): - pass - - -class VerifyAgent214(VerifyAgent): - pass - - -class VerifyAgent215(VerifyAgent45): - pass - - -class VerificationItem107(VerificationItem): - pass - - -class ReferenceAsset4(ReferenceAsset): - pass - - -class FeedFieldMapping3(FeedFieldMapping): - pass - - -class ReferenceAuthorization3(ReferenceAuthorization): - pass - - -class DeclaredBy108(DeclaredBy): - pass - - -class VerifyAgent216(VerifyAgent): - pass - - -class VerifyAgent217(VerifyAgent45): - pass - - -class VerificationItem108(VerificationItem): - pass - - -class DeclaredBy109(DeclaredBy): - pass - - -class VerifyAgent218(VerifyAgent): - pass - - -class VerifyAgent219(VerifyAgent45): - pass - - -class VerificationItem109(VerificationItem): - pass - - -class DeclaredBy110(DeclaredBy): - pass - - -class VerifyAgent220(VerifyAgent): - pass - - -class VerifyAgent221(VerifyAgent45): - pass - - -class VerificationItem110(VerificationItem): - pass - - -class DeclaredBy111(DeclaredBy): - pass - - -class VerifyAgent222(VerifyAgent): - pass - - -class VerifyAgent223(VerifyAgent45): - pass - - -class VerificationItem111(VerificationItem): - pass - - -class DeclaredBy112(DeclaredBy): - pass - - -class VerifyAgent224(VerifyAgent): - pass - - -class VerifyAgent225(VerifyAgent45): - pass - - -class VerificationItem112(VerificationItem): - pass - - -class DeclaredBy113(DeclaredBy): - pass - - -class VerifyAgent226(VerifyAgent): - pass - - -class VerifyAgent227(VerifyAgent45): - pass - - -class VerificationItem113(VerificationItem): - pass - - -class DeclaredBy114(DeclaredBy): - pass - - -class VerifyAgent228(VerifyAgent): - pass - - -class VerifyAgent229(VerifyAgent45): - pass - - -class VerificationItem114(VerificationItem): - pass - - -class DeclaredBy115(DeclaredBy): - pass - - -class VerifyAgent230(VerifyAgent): - pass - - -class VerifyAgent231(VerifyAgent45): - pass - - -class VerificationItem115(VerificationItem): - pass - - -class DeclaredBy116(DeclaredBy): - pass - - -class VerifyAgent232(VerifyAgent): - pass - - -class VerifyAgent233(VerifyAgent45): - pass - - -class VerificationItem116(VerificationItem): - pass - - -class Right1(Right): - pass - - -class IndustryIdentifier2(IndustryIdentifier): - pass - - -class DeclaredBy117(DeclaredBy): - pass - - -class VerifyAgent234(VerifyAgent): - pass - - -class VerifyAgent235(VerifyAgent45): - pass - - -class VerificationItem117(VerificationItem): - pass - - -class Type15(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class PropertyId(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Type15, - Field( - description='Type of identifier', - examples=[ - 'domain', - 'ios_bundle', - 'venue_id', - 'apple_podcast_id', - 'station_id', - 'facility_id', - ], - title='Property Identifier Types', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class Artifact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_id: Annotated[ - PropertyId, Field(description='Property where the artifact appears', title='Identifier') - ] - artifact_id: Annotated[str, Field(description='Artifact identifier within the property')] - - -class GenerationContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_type: Annotated[ - str | None, - Field( - description="Type of context that triggered generation (e.g., 'web_page', 'conversational', 'search', 'app', 'dooh')" - ), - ] = None - artifact: Annotated[ - Artifact | None, - Field( - description='Reference to the content-standards artifact that provided the generation context. Links this variant to the specific piece of content (article, video, podcast segment, etc.) where the ad was placed.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[int, Field(description='Maximum number of creatives requested', ge=1)] - offset: Annotated[int, Field(description='Number of creatives skipped', ge=0)] - has_more: Annotated[ - bool, Field(description='Whether more creatives are available beyond this page') - ] - total_count: Annotated[ - int | None, - Field( - description='Total number of creatives matching the request filters. Canonical field name (matches `PaginationResponse.total_count`). Sellers SHOULD populate this and the deprecated `total` field identically until 4.0; buyers SHOULD prefer this field.', - ge=0, - ), - ] = None - total: Annotated[ - int | None, - Field( - deprecated=True, - description='**Deprecated** — use `total_count` instead. Retained as a legacy alias for 3.x backward compatibility; removed in AdCP 4.0. Sellers populating this field MUST also populate `total_count` with the same value.', - ge=0, - ), - ] = None - - -class Issue10(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue10] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class ByEventTypeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: EventType - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[float, Field(description='Number of events of this type', ge=0.0)] - value: Annotated[ - float | None, Field(description='Total monetary value of events of this type', ge=0.0) - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class ByActionSourceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_source: ActionSource - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[ - float, Field(description='Number of conversions from this action source', ge=0.0) - ] - value: Annotated[ - float | None, - Field(description='Total monetary value of conversions from this action source', ge=0.0), - ] = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction23): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo9 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride9 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor1, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Totals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float | None, Field(description='Amount spent', ge=0.0)] = None - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction23): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo10 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride10 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor2 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction26(Jurisdiction23): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction26] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo11 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride11 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor3, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class EmbeddedProvenanceItem26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent52 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent53 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction27(Jurisdiction23): - pass - - -class Disclosure26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction27] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy26 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem26] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark26] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure26 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem26] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance26 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent54 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent55 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction28(Jurisdiction23): - pass - - -class Disclosure27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction28] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy27 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem27] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark27] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure27 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem27] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance27 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent56 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent57 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction29(Jurisdiction23): - pass - - -class Disclosure28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction29] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy28 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem28] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark28] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure28 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem28] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance28 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent58 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent59 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction30(Jurisdiction23): - pass - - -class Disclosure29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction30] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy29 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem29] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark29] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure29 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem29] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance29 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent60 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent61 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction31(Jurisdiction23): - pass - - -class Disclosure30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction31] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy30 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem30] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark30] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure30 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem30] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance30 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent62 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent63 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction32(Jurisdiction23): - pass - - -class Disclosure31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction32] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy31 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem31] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark31] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure31 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem31] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance31 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent64 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent65 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction33(Jurisdiction23): - pass - - -class Disclosure32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction33] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy32 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem32] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark32] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure32 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem32] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance32 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent66 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent67 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction34(Jurisdiction23): - pass - - -class Disclosure33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction34] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy33 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem33] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark33] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure33 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem33] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance33 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent68 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent69 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction35(Jurisdiction23): - pass - - -class Disclosure34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction35] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy34 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem34] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark34] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure34 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem34] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance34 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent70 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent71 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction36(Jurisdiction23): - pass - - -class Disclosure35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction36] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy35 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem35] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark35] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure35 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem35] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance35 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent72 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent73 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction37(Jurisdiction23): - pass - - -class Disclosure36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction37] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy36 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem36] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark36] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure36 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem36] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance36 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent74 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent75 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction38(Jurisdiction23): - pass - - -class Disclosure37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction38] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy37 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem37] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark37] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure37 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem37] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance37 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent76 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent77 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction39(Jurisdiction23): - pass - - -class Disclosure38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction39] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy38 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem38] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark38] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure38 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem38] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance38 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent78 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent79 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction40(Jurisdiction23): - pass - - -class Disclosure39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction40] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy39 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem39] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark39] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure39 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem39] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance39 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets69(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets70(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent80 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent81 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction42(Jurisdiction23): - pass - - -class Disclosure40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction42] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy40 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem40] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark40] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure40 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem40] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance40 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent82 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent83 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction43(Jurisdiction23): - pass - - -class Disclosure41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction43] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy41 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem41] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark41] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure41 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem41] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance41 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent84 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent85 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction44(Jurisdiction23): - pass - - -class Disclosure42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction44] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy42 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem42] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark42] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure42 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem42] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance42 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent86 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent87 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction45(Jurisdiction23): - pass - - -class Disclosure43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction45] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy43 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem43] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark43] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure43 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem43] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance43 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent88 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent89 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction46(Jurisdiction23): - pass - - -class Disclosure44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction46] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy44 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem44] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark44] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure44 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem44] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media1, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance44 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent90 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent91 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction47(Jurisdiction23): - pass - - -class Disclosure45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction47] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy45 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem45] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark45] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure45 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem45] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance45 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent92 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent93 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction48(Jurisdiction23): - pass - - -class Disclosure46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction48] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy46 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem46] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark46] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure46 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem46] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance46 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent94 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent95 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction49(Jurisdiction23): - pass - - -class Disclosure47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction49] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy47 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem47] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark47] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure47 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem47] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target2 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target2.linear - provenance: Annotated[ - Provenance47 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent96 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent97 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction50(Jurisdiction23): - pass - - -class Disclosure48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction50] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy48 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem48] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark48] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure48 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem48] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets762(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance48 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent98 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent99 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction51(Jurisdiction23): - pass - - -class Disclosure49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction51] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy49 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem49] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark49] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure49 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem49] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets763(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance49 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction52(Jurisdiction23): - pass - - -class Disclosure50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction52] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy50 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem50] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark50] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure50 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem50] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets764(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance50 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction53(Jurisdiction23): - pass - - -class Disclosure51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction53] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy51 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem51] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark51] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure51 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem51] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets765(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance51 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction54(Jurisdiction23): - pass - - -class Disclosure52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction54] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy52 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem52] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark52] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure52 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem52] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets766(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance52 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction55(Jurisdiction23): - pass - - -class Disclosure53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction55] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy53 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem53] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark53] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure53 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem53] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets767(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance53 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction56(Jurisdiction23): - pass - - -class Disclosure54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction56] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy54 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem54] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark54] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure54 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem54] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets768(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance54 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction57(Jurisdiction23): - pass - - -class Disclosure55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction57] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy55 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem55] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark55] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure55 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem55] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets769(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance55 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction58(Jurisdiction23): - pass - - -class Disclosure56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction58] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy56 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem56] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark56] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure56 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem56] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance56 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction59(Jurisdiction23): - pass - - -class Disclosure57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction59] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy57 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem57] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark57] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure57 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem57] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance57 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction60(Jurisdiction23): - pass - - -class Disclosure58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction60] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy58 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem58] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark58] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure58 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem58] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance58 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction61(Jurisdiction23): - pass - - -class Disclosure59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction61] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy59 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem59] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark59] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure59 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem59] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance59 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction62(Jurisdiction23): - pass - - -class Disclosure60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction62] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy60 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem60] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark60] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure60 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem60] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance60 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction63(Jurisdiction23): - pass - - -class Disclosure61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction63] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy61 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem61] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark61] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure61 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem61] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance61 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure2(RequiredDisclosure): - pass - - -class Compliance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure2] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets7617(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset2] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance2 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets7618(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping1] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction65(Jurisdiction23): - pass - - -class Disclosure62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction65] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy62 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem62] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark62] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure62 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem62] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance62 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction66(Jurisdiction23): - pass - - -class Disclosure63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction66] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy63 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem63] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark63] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure63 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem63] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance63 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction67(Jurisdiction23): - pass - - -class Disclosure64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction67] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy64 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem64] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark64] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure64 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem64] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance64 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction68(Jurisdiction23): - pass - - -class Disclosure65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction68] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy65 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem65] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark65] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure65 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem65] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance65 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction69(Jurisdiction23): - pass - - -class Disclosure66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction69] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy66 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem66] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark66] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure66 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem66] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media2 | Media3, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl1 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance66 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction70(Jurisdiction23): - pass - - -class Disclosure67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction70] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy67 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem67] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark67] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure67 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem67] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance67 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction71(Jurisdiction23): - pass - - -class Disclosure68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction71] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy68 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem68] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark68] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure68 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem68] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance68 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction72(Jurisdiction23): - pass - - -class Disclosure69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction72] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy69 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem69] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark69] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure69 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem69] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target2 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target2.linear - provenance: Annotated[ - Provenance69 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets761( - RootModel[ - Assets762 - | Assets763 - | Assets764 - | Assets765 - | Assets766 - | Assets767 - | Assets768 - | Assets769 - | Assets7610 - | Assets7611 - | Assets7612 - | Assets7613 - | Assets7614 - | Assets7615 - | Assets68 - | Assets7617 - | Assets7618 - | Assets7619 - | Assets7620 - | Assets7621 - | Assets7622 - | Assets7623 - ] -): - root: Annotated[ - Assets762 - | Assets763 - | Assets764 - | Assets765 - | Assets766 - | Assets767 - | Assets768 - | Assets769 - | Assets7610 - | Assets7611 - | Assets7612 - | Assets7613 - | Assets7614 - | Assets7615 - | Assets68 - | Assets7617 - | Assets7618 - | Assets7619 - | Assets7620 - | Assets7621 - | Assets7622 - | Assets7623, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets76(RootModel[list[Assets761]]): - root: Annotated[list[Assets761], Field(min_length=1)] - - -class EmbeddedProvenanceItem70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction73(Jurisdiction23): - pass - - -class Disclosure70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction73] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy70 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem70] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark70] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure70 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem70] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance70 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo12 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride12 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction74(Jurisdiction23): - pass - - -class Disclosure71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction74] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy71 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem71] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark71] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure71 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem71] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Manifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: Annotated[ - FormatKind | None, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets55 - | Assets56 - | Assets57 - | Assets58 - | Assets59 - | Assets60 - | Assets61 - | Assets62 - | Assets63 - | Assets64 - | Assets65 - | Assets66 - | Assets67 - | Assets68 - | Assets69 - | Assets70 - | Assets71 - | Assets72 - | Assets73 - | Assets74 - | Assets75 - | Assets76, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance71 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction75(Jurisdiction23): - pass - - -class Disclosure72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction75] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy72 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem72] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark72] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure72 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem72] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance72 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction76(Jurisdiction23): - pass - - -class Disclosure73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction76] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy73 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem73] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark73] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure73 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem73] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance73 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction77(Jurisdiction23): - pass - - -class Disclosure74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction77] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy74 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem74] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark74] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure74 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem74] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance74 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction78(Jurisdiction23): - pass - - -class Disclosure75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction78] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy75 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem75] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark75] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure75 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem75] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance75 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction79(Jurisdiction23): - pass - - -class Disclosure76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction79] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy76 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem76] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark76] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure76 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem76] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance76 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction80(Jurisdiction23): - pass - - -class Disclosure77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction80] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy77 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem77] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark77] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure77 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem77] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance77 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction81(Jurisdiction23): - pass - - -class Disclosure78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction81] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy78 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem78] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark78] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure78 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem78] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance78 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction82(Jurisdiction23): - pass - - -class Disclosure79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction82] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy79 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem79] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark79] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure79 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem79] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance79 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction83(Jurisdiction23): - pass - - -class Disclosure80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction83] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy80 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem80] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark80] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure80 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem80] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance80 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction84(Jurisdiction23): - pass - - -class Disclosure81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction84] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy81 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem81] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark81] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure81 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem81] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance81 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction85(Jurisdiction23): - pass - - -class Disclosure82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction85] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy82 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem82] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark82] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure82 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem82] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance82 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction86(Jurisdiction23): - pass - - -class Disclosure83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction86] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy83 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem83] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark83] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure83 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem83] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance83 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction87(Jurisdiction23): - pass - - -class Disclosure84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction87] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy84 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem84] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark84] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure84 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem84] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance84 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction88(Jurisdiction23): - pass - - -class Disclosure85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction88] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy85 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem85] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark85] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure85 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem85] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance85 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets91(Assets68): - pass - - -class RequiredDisclosure3(RequiredDisclosure): - pass - - -class Compliance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure3] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets92(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset3] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance3 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets93(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping2] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction90(Jurisdiction23): - pass - - -class Disclosure86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction90] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy86 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem86] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark86] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure86 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem86] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization2 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance86 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction91(Jurisdiction23): - pass - - -class Disclosure87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction91] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy87 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem87] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark87] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure87 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem87] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance87 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction92(Jurisdiction23): - pass - - -class Disclosure88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction92] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy88 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem88] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark88] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure88 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem88] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance88 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction93(Jurisdiction23): - pass - - -class Disclosure89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction93] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy89 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem89] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark89] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure89 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem89] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance89 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction94(Jurisdiction23): - pass - - -class Disclosure90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction94] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy90 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem90] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark90] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure90 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem90] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media4 | Media5, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl2 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance90 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction95(Jurisdiction23): - pass - - -class Disclosure91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction95] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy91 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem91] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark91] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure91 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem91] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance91 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction96(Jurisdiction23): - pass - - -class Disclosure92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction96] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy92 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem92] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark92] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure92 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem92] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance92 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction97(Jurisdiction23): - pass - - -class Disclosure93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction97] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy93 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem93] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark93] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure93 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem93] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target2 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target2.linear - provenance: Annotated[ - Provenance93 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction98(Jurisdiction23): - pass - - -class Disclosure94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction98] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy94 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem94] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark94] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure94 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem94] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets992(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance94 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction99(Jurisdiction23): - pass - - -class Disclosure95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction99] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy95 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem95] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark95] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure95 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem95] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets993(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance95 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent192 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent193 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction100(Jurisdiction23): - pass - - -class Disclosure96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction100] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy96 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem96] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark96] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure96 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem96] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets994(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance96 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent194 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent195 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction101(Jurisdiction23): - pass - - -class Disclosure97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction101] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy97 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem97] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark97] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure97 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem97] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets995(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance97 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent196 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent197 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction102(Jurisdiction23): - pass - - -class Disclosure98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction102] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy98 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem98] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark98] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure98 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem98] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets996(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance98 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent198 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent199 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction103(Jurisdiction23): - pass - - -class Disclosure99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction103] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy99 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem99] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark99] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure99 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem99] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets997(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance99 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent200 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent201 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction104(Jurisdiction23): - pass - - -class Disclosure100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction104] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy100 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem100] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark100] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure100 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem100] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets998(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance100 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent202 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent203 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction105(Jurisdiction23): - pass - - -class Disclosure101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction105] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy101 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem101] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark101] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure101 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem101] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets999(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance101 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent204 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent205 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction106(Jurisdiction23): - pass - - -class Disclosure102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction106] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy102 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem102] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark102] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure102 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem102] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance102 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent206 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent207 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction107(Jurisdiction23): - pass - - -class Disclosure103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction107] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy103 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem103] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark103] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure103 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem103] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance103 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent208 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent209 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction108(Jurisdiction23): - pass - - -class Disclosure104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction108] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy104 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem104] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark104] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure104 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem104] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance104 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent210 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent211 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction109(Jurisdiction23): - pass - - -class Disclosure105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction109] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy105 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem105] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark105] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure105 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem105] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance105 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent212 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent213 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction110(Jurisdiction23): - pass - - -class Disclosure106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction110] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy106 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem106] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark106] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure106 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem106] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance106 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent214 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent215 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction111(Jurisdiction23): - pass - - -class Disclosure107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction111] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy107 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem107] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark107] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure107 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem107] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance107 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure4(RequiredDisclosure): - pass - - -class Compliance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure4] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets9917(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset4] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance4 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets9918(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping3] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent216 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent217 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction113(Jurisdiction23): - pass - - -class Disclosure108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction113] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy108 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem108] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark108] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure108 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem108] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization3 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance108 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent218 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent219 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction114(Jurisdiction23): - pass - - -class Disclosure109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction114] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy109 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem109] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark109] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure109 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem109] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance109 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent220 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent221 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction115(Jurisdiction23): - pass - - -class Disclosure110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction115] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy110 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem110] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark110] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure110 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem110] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance110 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent222 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent223 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction116(Jurisdiction23): - pass - - -class Disclosure111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction116] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy111 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem111] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark111] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure111 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem111] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance111 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent224 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent225 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction117(Jurisdiction23): - pass - - -class Disclosure112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction117] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy112 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem112] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark112] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure112 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem112] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media6 | Media7, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl3 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance112 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent226 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent227 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction118(Jurisdiction23): - pass - - -class Disclosure113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction118] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy113 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem113] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark113] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure113 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem113] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance113 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent228 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent229 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction119(Jurisdiction23): - pass - - -class Disclosure114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction119] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy114 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem114] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark114] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure114 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem114] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance114 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent230 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent231 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction120(Jurisdiction23): - pass - - -class Disclosure115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction120] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy115 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem115] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark115] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure115 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem115] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target2 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target2.linear - provenance: Annotated[ - Provenance115 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets991( - RootModel[ - Assets992 - | Assets993 - | Assets994 - | Assets995 - | Assets996 - | Assets997 - | Assets998 - | Assets999 - | Assets9910 - | Assets9911 - | Assets9912 - | Assets9913 - | Assets9914 - | Assets9915 - | Assets68 - | Assets9917 - | Assets9918 - | Assets9919 - | Assets9920 - | Assets9921 - | Assets9922 - | Assets9923 - ] -): - root: Annotated[ - Assets992 - | Assets993 - | Assets994 - | Assets995 - | Assets996 - | Assets997 - | Assets998 - | Assets999 - | Assets9910 - | Assets9911 - | Assets9912 - | Assets9913 - | Assets9914 - | Assets9915 - | Assets68 - | Assets9917 - | Assets9918 - | Assets9919 - | Assets9920 - | Assets9921 - | Assets9922 - | Assets9923, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets99(RootModel[list[Assets991]]): - root: Annotated[list[Assets991], Field(min_length=1)] - - -class EmbeddedProvenanceItem116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent232 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent233 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction121(Jurisdiction23): - pass - - -class Disclosure116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction121] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy116 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem116] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark116] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure116 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem116] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance116 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo13 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride13 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent234 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent235 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction122(Jurisdiction23): - pass - - -class Disclosure117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction122] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy117 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem117] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark117] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure117 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem117] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Manifest1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: Annotated[ - FormatKind, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets77 - | Assets78 - | Assets79 - | Assets80 - | Assets81 - | Assets82 - | Assets83 - | Assets84 - | Assets85 - | Assets86 - | Assets87 - | Assets88 - | Assets89 - | Assets90 - | Assets91 - | Assets92 - | Assets93 - | Assets94 - | Assets95 - | Assets96 - | Assets97 - | Assets98 - | Assets99, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand11 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right1] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier2] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance117 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Variant(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float | None, Field(description='Amount spent', ge=0.0)] = None - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow3 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics2 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability4 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue2] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - variant_id: Annotated[str, Field(description='Platform-assigned identifier for this variant')] - manifest: Annotated[ - Manifest | Manifest1 | None, - Field( - description='The rendered creative manifest for this variant — the actual output that was served, not the input assets. Contains format_id and the resolved assets (specific headline, image, video, etc. the platform selected or generated). For Tier 2, shows which asset combination was picked. For Tier 3, contains the generated assets which may differ entirely from the input brand identity. Pass to preview_creative to re-render.', - title='Creative Manifest', - ), - ] = None - generation_context: Annotated[ - GenerationContext | None, - Field( - description='Input signals that triggered generation of this variant (Tier 3). Describes why the platform created this specific variant. Platforms should provide summarized or anonymized signals rather than raw user input. For web contexts, may include page topic or URL. For conversational contexts, an anonymized content signal. For search, query category or intent. When the content context is managed through AdCP content standards, reference the artifact directly via the artifact field.' - ), - ] = None - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Creative identifier')] - media_buy_id: Annotated[ - str | None, - Field( - description="Publisher's media buy identifier for this creative. Present when the request spanned multiple media buys, so the buyer can correlate each creative to its media buy." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field(description='Format of this creative', title='Format Reference (Structured Object)'), - ] = None - totals: Annotated[ - Totals | None, - Field( - description='Aggregate delivery metrics across all variants of this creative', - title='Delivery Metrics', - ), - ] = None - variant_count: Annotated[ - int | None, - Field( - description='Total number of variants for this creative. When max_variants was specified in the request, this may exceed the number of items in the variants array.', - ge=0, - ), - ] = None - variants: Annotated[ - list[Variant], - Field( - description='Variant-level delivery breakdown. Each variant includes the rendered manifest and delivery metrics. For standard creatives, contains a single variant. For asset group optimization, one per combination. For generative creative, one per generated execution. Empty when a creative has no variants yet.' - ), - ] - - -class GetCreativeDeliveryResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - account_id: Annotated[ - str | None, - Field( - description='Account identifier. Present when the response spans or is scoped to a specific account.' - ), - ] = None - media_buy_id: Annotated[ - str | None, - Field( - description="Publisher's media buy identifier. Present when the request was scoped to a single media buy." - ), - ] = None - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code for monetary values in this response (e.g., 'USD', 'EUR')", - pattern='^[A-Z]{3}$', - ), - ] - reporting_period: Annotated[ReportingPeriod, Field(description='Date range for the report.')] - creatives: Annotated[ - Sequence[Creative], Field(description='Creative delivery data with variant breakdowns') - ] - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination information. Present when the request included pagination parameters. **Note:** `get_creative_delivery` uses page-based pagination (`limit`/`offset`) for historical reasons, distinct from the cursor-based [`PaginationResponse`](/schemas/v3/core/pagination-response.json) used by `list_*` tools. Field naming aligned with `PaginationResponse.total_count` in 3.1; the legacy `total` field is retained as a deprecated alias until 4.0. Sellers MUST populate both fields identically; buyers SHOULD prefer `total_count` (the canonical name) and ignore `total` if both are present.' - ), - ] = None - errors: Annotated[ - list[Error] | None, Field(description='Task-specific errors and warnings') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/get_creative_features_request.py b/src/adcp/types/generated_poc/bundled/creative/get_creative_features_request.py deleted file mode 100644 index d10ca236..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/get_creative_features_request.py +++ /dev/null @@ -1,21298 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/get_creative_features_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent237(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy119(DeclaredBy): - pass - - -class VerifyAgent238(VerifyAgent): - pass - - -class VerifyAgent239(VerifyAgent237): - pass - - -class VerificationItem119(VerificationItem): - pass - - -class DeclaredBy120(DeclaredBy): - pass - - -class VerifyAgent240(VerifyAgent): - pass - - -class VerifyAgent241(VerifyAgent237): - pass - - -class VerificationItem120(VerificationItem): - pass - - -class DeclaredBy121(DeclaredBy): - pass - - -class VerifyAgent242(VerifyAgent): - pass - - -class VerifyAgent243(VerifyAgent237): - pass - - -class VerificationItem121(VerificationItem): - pass - - -class DeclaredBy122(DeclaredBy): - pass - - -class VerifyAgent244(VerifyAgent): - pass - - -class VerifyAgent245(VerifyAgent237): - pass - - -class VerificationItem122(VerificationItem): - pass - - -class DeclaredBy123(DeclaredBy): - pass - - -class VerifyAgent246(VerifyAgent): - pass - - -class VerifyAgent247(VerifyAgent237): - pass - - -class VerificationItem123(VerificationItem): - pass - - -class DeclaredBy124(DeclaredBy): - pass - - -class VerifyAgent248(VerifyAgent): - pass - - -class VerifyAgent249(VerifyAgent237): - pass - - -class VerificationItem124(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy125(DeclaredBy): - pass - - -class VerifyAgent250(VerifyAgent): - pass - - -class VerifyAgent251(VerifyAgent237): - pass - - -class VerificationItem125(VerificationItem): - pass - - -class DeclaredBy126(DeclaredBy): - pass - - -class VerifyAgent252(VerifyAgent): - pass - - -class VerifyAgent253(VerifyAgent237): - pass - - -class VerificationItem126(VerificationItem): - pass - - -class DeclaredBy127(DeclaredBy): - pass - - -class VerifyAgent254(VerifyAgent): - pass - - -class VerifyAgent255(VerifyAgent237): - pass - - -class VerificationItem127(VerificationItem): - pass - - -class DeclaredBy128(DeclaredBy): - pass - - -class VerifyAgent256(VerifyAgent): - pass - - -class VerifyAgent257(VerifyAgent237): - pass - - -class VerificationItem128(VerificationItem): - pass - - -class DeclaredBy129(DeclaredBy): - pass - - -class VerifyAgent258(VerifyAgent): - pass - - -class VerifyAgent259(VerifyAgent237): - pass - - -class VerificationItem129(VerificationItem): - pass - - -class DeclaredBy130(DeclaredBy): - pass - - -class VerifyAgent260(VerifyAgent): - pass - - -class VerifyAgent261(VerifyAgent237): - pass - - -class VerificationItem130(VerificationItem): - pass - - -class DeclaredBy131(DeclaredBy): - pass - - -class VerifyAgent262(VerifyAgent): - pass - - -class VerifyAgent263(VerifyAgent237): - pass - - -class VerificationItem131(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role141(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role141, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy132(DeclaredBy): - pass - - -class VerifyAgent264(VerifyAgent): - pass - - -class VerifyAgent265(VerifyAgent237): - pass - - -class VerificationItem132(VerificationItem): - pass - - -class DeclaredBy133(DeclaredBy): - pass - - -class VerifyAgent266(VerifyAgent): - pass - - -class VerifyAgent267(VerifyAgent237): - pass - - -class VerificationItem133(VerificationItem): - pass - - -class DeclaredBy134(DeclaredBy): - pass - - -class VerifyAgent268(VerifyAgent): - pass - - -class VerifyAgent269(VerifyAgent237): - pass - - -class VerificationItem134(VerificationItem): - pass - - -class DeclaredBy135(DeclaredBy): - pass - - -class VerifyAgent270(VerifyAgent): - pass - - -class VerifyAgent271(VerifyAgent237): - pass - - -class VerificationItem135(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy136(DeclaredBy): - pass - - -class VerifyAgent272(VerifyAgent): - pass - - -class VerifyAgent273(VerifyAgent237): - pass - - -class VerificationItem136(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy137(DeclaredBy): - pass - - -class VerifyAgent274(VerifyAgent): - pass - - -class VerifyAgent275(VerifyAgent237): - pass - - -class VerificationItem137(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy138(DeclaredBy): - pass - - -class VerifyAgent276(VerifyAgent): - pass - - -class VerifyAgent277(VerifyAgent237): - pass - - -class VerificationItem138(VerificationItem): - pass - - -class Target10(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy139(DeclaredBy): - pass - - -class VerifyAgent278(VerifyAgent): - pass - - -class VerifyAgent279(VerifyAgent237): - pass - - -class VerificationItem139(VerificationItem): - pass - - -class DeclaredBy140(DeclaredBy): - pass - - -class VerifyAgent280(VerifyAgent): - pass - - -class VerifyAgent281(VerifyAgent237): - pass - - -class VerificationItem140(VerificationItem): - pass - - -class DeclaredBy141(DeclaredBy): - pass - - -class VerifyAgent282(VerifyAgent): - pass - - -class VerifyAgent283(VerifyAgent237): - pass - - -class VerificationItem141(VerificationItem): - pass - - -class DeclaredBy142(DeclaredBy): - pass - - -class VerifyAgent284(VerifyAgent): - pass - - -class VerifyAgent285(VerifyAgent237): - pass - - -class VerificationItem142(VerificationItem): - pass - - -class DeclaredBy143(DeclaredBy): - pass - - -class VerifyAgent286(VerifyAgent): - pass - - -class VerifyAgent287(VerifyAgent237): - pass - - -class VerificationItem143(VerificationItem): - pass - - -class DeclaredBy144(DeclaredBy): - pass - - -class VerifyAgent288(VerifyAgent): - pass - - -class VerifyAgent289(VerifyAgent237): - pass - - -class VerificationItem144(VerificationItem): - pass - - -class DeclaredBy145(DeclaredBy): - pass - - -class VerifyAgent290(VerifyAgent): - pass - - -class VerifyAgent291(VerifyAgent237): - pass - - -class VerificationItem145(VerificationItem): - pass - - -class DeclaredBy146(DeclaredBy): - pass - - -class VerifyAgent292(VerifyAgent): - pass - - -class VerifyAgent293(VerifyAgent237): - pass - - -class VerificationItem146(VerificationItem): - pass - - -class DeclaredBy147(DeclaredBy): - pass - - -class VerifyAgent294(VerifyAgent): - pass - - -class VerifyAgent295(VerifyAgent237): - pass - - -class VerificationItem147(VerificationItem): - pass - - -class DeclaredBy148(DeclaredBy): - pass - - -class VerifyAgent296(VerifyAgent): - pass - - -class VerifyAgent297(VerifyAgent237): - pass - - -class VerificationItem148(VerificationItem): - pass - - -class DeclaredBy149(DeclaredBy): - pass - - -class VerifyAgent298(VerifyAgent): - pass - - -class VerifyAgent299(VerifyAgent237): - pass - - -class VerificationItem149(VerificationItem): - pass - - -class DeclaredBy150(DeclaredBy): - pass - - -class VerifyAgent300(VerifyAgent): - pass - - -class VerifyAgent301(VerifyAgent237): - pass - - -class VerificationItem150(VerificationItem): - pass - - -class DeclaredBy151(DeclaredBy): - pass - - -class VerifyAgent302(VerifyAgent): - pass - - -class VerifyAgent303(VerifyAgent237): - pass - - -class VerificationItem151(VerificationItem): - pass - - -class DeclaredBy152(DeclaredBy): - pass - - -class VerifyAgent304(VerifyAgent): - pass - - -class VerifyAgent305(VerifyAgent237): - pass - - -class VerificationItem152(VerificationItem): - pass - - -class DeclaredBy153(DeclaredBy): - pass - - -class VerifyAgent306(VerifyAgent): - pass - - -class VerifyAgent307(VerifyAgent237): - pass - - -class VerificationItem153(VerificationItem): - pass - - -class ReferenceAsset6(ReferenceAsset): - pass - - -class FeedFieldMapping5(FeedFieldMapping): - pass - - -class ReferenceAuthorization5(ReferenceAuthorization): - pass - - -class DeclaredBy154(DeclaredBy): - pass - - -class VerifyAgent308(VerifyAgent): - pass - - -class VerifyAgent309(VerifyAgent237): - pass - - -class VerificationItem154(VerificationItem): - pass - - -class DeclaredBy155(DeclaredBy): - pass - - -class VerifyAgent310(VerifyAgent): - pass - - -class VerifyAgent311(VerifyAgent237): - pass - - -class VerificationItem155(VerificationItem): - pass - - -class DeclaredBy156(DeclaredBy): - pass - - -class VerifyAgent312(VerifyAgent): - pass - - -class VerifyAgent313(VerifyAgent237): - pass - - -class VerificationItem156(VerificationItem): - pass - - -class DeclaredBy157(DeclaredBy): - pass - - -class VerifyAgent314(VerifyAgent): - pass - - -class VerifyAgent315(VerifyAgent237): - pass - - -class VerificationItem157(VerificationItem): - pass - - -class DeclaredBy158(DeclaredBy): - pass - - -class VerifyAgent316(VerifyAgent): - pass - - -class VerifyAgent317(VerifyAgent237): - pass - - -class VerificationItem158(VerificationItem): - pass - - -class DeclaredBy159(DeclaredBy): - pass - - -class VerifyAgent318(VerifyAgent): - pass - - -class VerifyAgent319(VerifyAgent237): - pass - - -class VerificationItem159(VerificationItem): - pass - - -class DeclaredBy160(DeclaredBy): - pass - - -class VerifyAgent320(VerifyAgent): - pass - - -class VerifyAgent321(VerifyAgent237): - pass - - -class VerificationItem160(VerificationItem): - pass - - -class DeclaredBy161(DeclaredBy): - pass - - -class VerifyAgent322(VerifyAgent): - pass - - -class VerifyAgent323(VerifyAgent237): - pass - - -class VerificationItem161(VerificationItem): - pass - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DeclaredBy162(DeclaredBy): - pass - - -class VerifyAgent324(VerifyAgent): - pass - - -class VerifyAgent325(VerifyAgent237): - pass - - -class VerificationItem162(VerificationItem): - pass - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Use(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ExcludedCountry(Country): - pass - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class Right(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[Use], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: Annotated[ - RightType | None, - Field( - description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.', - title='Right Type', - ), - ] = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Type(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy163(DeclaredBy): - pass - - -class VerifyAgent326(VerifyAgent): - pass - - -class VerifyAgent327(VerifyAgent237): - pass - - -class VerificationItem163(VerificationItem): - pass - - - -class DeclaredBy164(DeclaredBy): - pass - - -class VerifyAgent328(VerifyAgent): - pass - - -class VerifyAgent329(VerifyAgent237): - pass - - -class VerificationItem164(VerificationItem): - pass - - -class DeclaredBy165(DeclaredBy): - pass - - -class VerifyAgent330(VerifyAgent): - pass - - -class VerifyAgent331(VerifyAgent237): - pass - - -class VerificationItem165(VerificationItem): - pass - - -class DeclaredBy166(DeclaredBy): - pass - - -class VerifyAgent332(VerifyAgent): - pass - - -class VerifyAgent333(VerifyAgent237): - pass - - -class VerificationItem166(VerificationItem): - pass - - -class DeclaredBy167(DeclaredBy): - pass - - -class VerifyAgent334(VerifyAgent): - pass - - -class VerifyAgent335(VerifyAgent237): - pass - - -class VerificationItem167(VerificationItem): - pass - - -class DeclaredBy168(DeclaredBy): - pass - - -class VerifyAgent336(VerifyAgent): - pass - - -class VerifyAgent337(VerifyAgent237): - pass - - -class VerificationItem168(VerificationItem): - pass - - -class DeclaredBy169(DeclaredBy): - pass - - -class VerifyAgent338(VerifyAgent): - pass - - -class VerifyAgent339(VerifyAgent237): - pass - - -class VerificationItem169(VerificationItem): - pass - - -class DeclaredBy170(DeclaredBy): - pass - - -class VerifyAgent340(VerifyAgent): - pass - - -class VerifyAgent341(VerifyAgent237): - pass - - -class VerificationItem170(VerificationItem): - pass - - -class DeclaredBy171(DeclaredBy): - pass - - -class VerifyAgent342(VerifyAgent): - pass - - -class VerifyAgent343(VerifyAgent237): - pass - - -class VerificationItem171(VerificationItem): - pass - - -class DeclaredBy172(DeclaredBy): - pass - - -class VerifyAgent344(VerifyAgent): - pass - - -class VerifyAgent345(VerifyAgent237): - pass - - -class VerificationItem172(VerificationItem): - pass - - -class DeclaredBy173(DeclaredBy): - pass - - -class VerifyAgent346(VerifyAgent): - pass - - -class VerifyAgent347(VerifyAgent237): - pass - - -class VerificationItem173(VerificationItem): - pass - - -class DeclaredBy174(DeclaredBy): - pass - - -class VerifyAgent348(VerifyAgent): - pass - - -class VerifyAgent349(VerifyAgent237): - pass - - -class VerificationItem174(VerificationItem): - pass - - -class DeclaredBy175(DeclaredBy): - pass - - -class VerifyAgent350(VerifyAgent): - pass - - -class VerifyAgent351(VerifyAgent237): - pass - - -class VerificationItem175(VerificationItem): - pass - - -class DeclaredBy176(DeclaredBy): - pass - - -class VerifyAgent352(VerifyAgent): - pass - - -class VerifyAgent353(VerifyAgent237): - pass - - -class VerificationItem176(VerificationItem): - pass - - -class DeclaredBy177(DeclaredBy): - pass - - -class VerifyAgent354(VerifyAgent): - pass - - -class VerifyAgent355(VerifyAgent237): - pass - - -class VerificationItem177(VerificationItem): - pass - - -class ReferenceAsset7(ReferenceAsset): - pass - - -class FeedFieldMapping6(FeedFieldMapping): - pass - - -class ReferenceAuthorization6(ReferenceAuthorization): - pass - - -class DeclaredBy178(DeclaredBy): - pass - - -class VerifyAgent356(VerifyAgent): - pass - - -class VerifyAgent357(VerifyAgent237): - pass - - -class VerificationItem178(VerificationItem): - pass - - -class DeclaredBy179(DeclaredBy): - pass - - -class VerifyAgent358(VerifyAgent): - pass - - -class VerifyAgent359(VerifyAgent237): - pass - - -class VerificationItem179(VerificationItem): - pass - - -class DeclaredBy180(DeclaredBy): - pass - - -class VerifyAgent360(VerifyAgent): - pass - - -class VerifyAgent361(VerifyAgent237): - pass - - -class VerificationItem180(VerificationItem): - pass - - -class DeclaredBy181(DeclaredBy): - pass - - -class VerifyAgent362(VerifyAgent): - pass - - -class VerifyAgent363(VerifyAgent237): - pass - - -class VerificationItem181(VerificationItem): - pass - - -class DeclaredBy182(DeclaredBy): - pass - - -class VerifyAgent364(VerifyAgent): - pass - - -class VerifyAgent365(VerifyAgent237): - pass - - -class VerificationItem182(VerificationItem): - pass - - -class DeclaredBy183(DeclaredBy): - pass - - -class VerifyAgent366(VerifyAgent): - pass - - -class VerifyAgent367(VerifyAgent237): - pass - - -class VerificationItem183(VerificationItem): - pass - - -class DeclaredBy184(DeclaredBy): - pass - - -class VerifyAgent368(VerifyAgent): - pass - - -class VerifyAgent369(VerifyAgent237): - pass - - -class VerificationItem184(VerificationItem): - pass - - -class DeclaredBy185(DeclaredBy): - pass - - -class VerifyAgent370(VerifyAgent): - pass - - -class VerifyAgent371(VerifyAgent237): - pass - - -class VerificationItem185(VerificationItem): - pass - - -class DeclaredBy186(DeclaredBy): - pass - - -class VerifyAgent372(VerifyAgent): - pass - - -class VerifyAgent373(VerifyAgent237): - pass - - -class VerificationItem186(VerificationItem): - pass - - -class DeclaredBy187(DeclaredBy): - pass - - -class VerifyAgent374(VerifyAgent): - pass - - -class VerifyAgent375(VerifyAgent237): - pass - - -class VerificationItem187(VerificationItem): - pass - - -class DeclaredBy188(DeclaredBy): - pass - - -class VerifyAgent376(VerifyAgent): - pass - - -class VerifyAgent377(VerifyAgent237): - pass - - -class VerificationItem188(VerificationItem): - pass - - -class DeclaredBy189(DeclaredBy): - pass - - -class VerifyAgent378(VerifyAgent): - pass - - -class VerifyAgent379(VerifyAgent237): - pass - - -class VerificationItem189(VerificationItem): - pass - - -class DeclaredBy190(DeclaredBy): - pass - - -class VerifyAgent380(VerifyAgent): - pass - - -class VerifyAgent381(VerifyAgent237): - pass - - -class VerificationItem190(VerificationItem): - pass - - -class DeclaredBy191(DeclaredBy): - pass - - -class VerifyAgent382(VerifyAgent): - pass - - -class VerifyAgent383(VerifyAgent237): - pass - - -class VerificationItem191(VerificationItem): - pass - - -class DeclaredBy192(DeclaredBy): - pass - - -class VerifyAgent384(VerifyAgent): - pass - - -class VerifyAgent385(VerifyAgent237): - pass - - -class VerificationItem192(VerificationItem): - pass - - -class DeclaredBy193(DeclaredBy): - pass - - -class VerifyAgent386(VerifyAgent): - pass - - -class VerifyAgent387(VerifyAgent237): - pass - - -class VerificationItem193(VerificationItem): - pass - - -class DeclaredBy194(DeclaredBy): - pass - - -class VerifyAgent388(VerifyAgent): - pass - - -class VerifyAgent389(VerifyAgent237): - pass - - -class VerificationItem194(VerificationItem): - pass - - -class DeclaredBy195(DeclaredBy): - pass - - -class VerifyAgent390(VerifyAgent): - pass - - -class VerifyAgent391(VerifyAgent237): - pass - - -class VerificationItem195(VerificationItem): - pass - - -class DeclaredBy196(DeclaredBy): - pass - - -class VerifyAgent392(VerifyAgent): - pass - - -class VerifyAgent393(VerifyAgent237): - pass - - -class VerificationItem196(VerificationItem): - pass - - -class DeclaredBy197(DeclaredBy): - pass - - -class VerifyAgent394(VerifyAgent): - pass - - -class VerifyAgent395(VerifyAgent237): - pass - - -class VerificationItem197(VerificationItem): - pass - - -class DeclaredBy198(DeclaredBy): - pass - - -class VerifyAgent396(VerifyAgent): - pass - - -class VerifyAgent397(VerifyAgent237): - pass - - -class VerificationItem198(VerificationItem): - pass - - -class DeclaredBy199(DeclaredBy): - pass - - -class VerifyAgent398(VerifyAgent): - pass - - -class VerifyAgent399(VerifyAgent237): - pass - - -class VerificationItem199(VerificationItem): - pass - - -class ReferenceAsset8(ReferenceAsset): - pass - - -class FeedFieldMapping7(FeedFieldMapping): - pass - - -class ReferenceAuthorization7(ReferenceAuthorization): - pass - - -class DeclaredBy200(DeclaredBy): - pass - - -class VerifyAgent400(VerifyAgent): - pass - - -class VerifyAgent401(VerifyAgent237): - pass - - -class VerificationItem200(VerificationItem): - pass - - -class DeclaredBy201(DeclaredBy): - pass - - -class VerifyAgent402(VerifyAgent): - pass - - -class VerifyAgent403(VerifyAgent237): - pass - - -class VerificationItem201(VerificationItem): - pass - - -class DeclaredBy202(DeclaredBy): - pass - - -class VerifyAgent404(VerifyAgent): - pass - - -class VerifyAgent405(VerifyAgent237): - pass - - -class VerificationItem202(VerificationItem): - pass - - -class DeclaredBy203(DeclaredBy): - pass - - -class VerifyAgent406(VerifyAgent): - pass - - -class VerifyAgent407(VerifyAgent237): - pass - - -class VerificationItem203(VerificationItem): - pass - - -class DeclaredBy204(DeclaredBy): - pass - - -class VerifyAgent408(VerifyAgent): - pass - - -class VerifyAgent409(VerifyAgent237): - pass - - -class VerificationItem204(VerificationItem): - pass - - -class DeclaredBy205(DeclaredBy): - pass - - -class VerifyAgent410(VerifyAgent): - pass - - -class VerifyAgent411(VerifyAgent237): - pass - - -class VerificationItem205(VerificationItem): - pass - - -class DeclaredBy206(DeclaredBy): - pass - - -class VerifyAgent412(VerifyAgent): - pass - - -class VerifyAgent413(VerifyAgent237): - pass - - -class VerificationItem206(VerificationItem): - pass - - -class DeclaredBy207(DeclaredBy): - pass - - -class VerifyAgent414(VerifyAgent): - pass - - -class VerifyAgent415(VerifyAgent237): - pass - - -class VerificationItem207(VerificationItem): - pass - - -class DeclaredBy208(DeclaredBy): - pass - - -class VerifyAgent416(VerifyAgent): - pass - - -class VerifyAgent417(VerifyAgent237): - pass - - -class VerificationItem208(VerificationItem): - pass - - -class Right3(Right): - pass - - -class IndustryIdentifier4(IndustryIdentifier): - pass - - -class DeclaredBy209(DeclaredBy): - pass - - -class VerifyAgent418(VerifyAgent): - pass - - -class VerifyAgent419(VerifyAgent237): - pass - - -class VerificationItem209(VerificationItem): - pass - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DeclaredBy210(DeclaredBy): - pass - - -class VerifyAgent420(VerifyAgent): - pass - - -class VerifyAgent421(VerifyAgent237): - pass - - -class VerificationItem210(VerificationItem): - pass - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent237 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction123] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent238 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent239 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction124(Jurisdiction123): - pass - - -class Disclosure119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction124] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy119 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem119] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark119] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure119 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem119] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance119 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent240 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent241 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction125(Jurisdiction123): - pass - - -class Disclosure120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction125] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy120 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem120] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark120] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure120 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem120] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance120 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent242 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent243 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction126(Jurisdiction123): - pass - - -class Disclosure121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction126] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy121 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem121] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark121] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure121 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem121] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance121 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent244 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent245 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction127(Jurisdiction123): - pass - - -class Disclosure122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction127] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy122 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem122] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark122] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure122 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem122] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance122 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent246 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent247 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction128(Jurisdiction123): - pass - - -class Disclosure123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction128] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy123 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem123] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark123] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure123 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem123] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance123 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent248 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent249 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction129(Jurisdiction123): - pass - - -class Disclosure124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction129] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy124 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem124] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark124] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure124 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem124] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance124 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent250 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent251 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction130(Jurisdiction123): - pass - - -class Disclosure125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction130] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy125 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem125] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark125] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure125 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem125] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance125 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent252 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent253 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction131(Jurisdiction123): - pass - - -class Disclosure126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction131] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy126 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem126] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark126] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure126 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem126] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance126 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent254 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent255 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction132(Jurisdiction123): - pass - - -class Disclosure127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction132] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy127 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem127] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark127] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure127 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem127] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance127 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent256 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent257 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction133(Jurisdiction123): - pass - - -class Disclosure128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction133] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy128 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem128] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark128] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure128 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem128] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance128 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent258 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent259 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction134(Jurisdiction123): - pass - - -class Disclosure129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction134] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy129 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem129] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark129] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure129 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem129] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance129 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent260 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent261 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction135(Jurisdiction123): - pass - - -class Disclosure130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction135] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy130 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem130] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark130] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure130 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem130] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance130 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent262 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent263 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction136(Jurisdiction123): - pass - - -class Disclosure131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction136] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy131 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem131] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark131] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure131 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem131] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance131 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets115(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets116(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent264 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent265 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction138(Jurisdiction123): - pass - - -class Disclosure132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction138] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy132 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem132] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark132] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure132 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem132] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance132 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent266 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent267 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction139(Jurisdiction123): - pass - - -class Disclosure133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction139] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy133 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem133] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark133] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure133 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem133] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance133 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent268 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent269 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction140(Jurisdiction123): - pass - - -class Disclosure134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction140] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy134 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem134] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark134] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure134 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem134] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance134 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent270 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent271 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction141(Jurisdiction123): - pass - - -class Disclosure135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction141] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy135 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem135] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark135] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure135 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem135] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance135 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent272 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent273 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction142(Jurisdiction123): - pass - - -class Disclosure136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction142] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy136 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem136] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark136] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure136 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem136] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media9, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance136 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent274 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent275 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction143(Jurisdiction123): - pass - - -class Disclosure137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction143] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy137 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem137] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark137] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure137 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem137] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance137 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent276 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent277 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction144(Jurisdiction123): - pass - - -class Disclosure138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction144] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy138 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem138] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark138] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure138 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem138] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance138 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent278 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent279 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction145(Jurisdiction123): - pass - - -class Disclosure139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction145] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy139 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem139] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark139] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure139 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem139] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target10 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target10.linear - provenance: Annotated[ - Provenance139 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent280 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent281 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction146(Jurisdiction123): - pass - - -class Disclosure140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction146] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy140 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem140] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark140] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure140 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem140] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance140 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent282 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent283 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction147(Jurisdiction123): - pass - - -class Disclosure141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction147] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy141 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem141] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark141] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure141 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem141] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance141 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent284 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent285 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction148(Jurisdiction123): - pass - - -class Disclosure142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction148] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy142 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem142] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark142] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure142 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem142] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance142 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent286 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent287 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction149(Jurisdiction123): - pass - - -class Disclosure143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction149] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy143 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem143] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark143] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure143 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem143] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance143 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent288 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent289 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction150(Jurisdiction123): - pass - - -class Disclosure144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction150] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy144 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem144] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark144] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure144 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem144] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance144 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent290 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent291 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction151(Jurisdiction123): - pass - - -class Disclosure145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction151] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy145 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem145] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark145] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure145 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem145] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance145 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent292 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent293 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction152(Jurisdiction123): - pass - - -class Disclosure146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction152] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy146 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem146] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark146] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure146 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem146] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance146 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent294 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent295 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction153(Jurisdiction123): - pass - - -class Disclosure147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction153] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy147 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem147] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark147] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure147 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem147] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance147 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent296 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent297 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction154(Jurisdiction123): - pass - - -class Disclosure148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction154] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy148 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem148] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark148] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure148 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem148] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance148 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent298 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent299 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction155(Jurisdiction123): - pass - - -class Disclosure149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction155] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy149 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem149] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark149] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure149 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem149] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance149 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent300 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent301 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction156(Jurisdiction123): - pass - - -class Disclosure150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction156] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy150 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem150] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark150] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure150 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem150] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance150 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent302 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent303 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction157(Jurisdiction123): - pass - - -class Disclosure151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction157] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy151 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem151] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark151] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure151 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem151] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance151 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent304 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent305 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction158(Jurisdiction123): - pass - - -class Disclosure152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction158] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy152 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem152] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark152] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure152 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem152] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance152 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent306 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent307 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction159(Jurisdiction123): - pass - - -class Disclosure153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction159] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy153 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem153] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark153] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure153 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem153] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance153 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure6(RequiredDisclosure): - pass - - -class Compliance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure6] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets12217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset6] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance6 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets12218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping5] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent308 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent309 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction161(Jurisdiction123): - pass - - -class Disclosure154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction161] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy154 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem154] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark154] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure154 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem154] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization5 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance154 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent310 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent311 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction162(Jurisdiction123): - pass - - -class Disclosure155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction162] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy155 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem155] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark155] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure155 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem155] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance155 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent312 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent313 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction163(Jurisdiction123): - pass - - -class Disclosure156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction163] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy156 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem156] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark156] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure156 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem156] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance156 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent314 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent315 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction164(Jurisdiction123): - pass - - -class Disclosure157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction164] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy157 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem157] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark157] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure157 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem157] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance157 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent316 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent317 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction165(Jurisdiction123): - pass - - -class Disclosure158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction165] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy158 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem158] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark158] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure158 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem158] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media10 | Media11, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl5 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance158 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent318 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent319 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction166(Jurisdiction123): - pass - - -class Disclosure159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction166] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy159 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem159] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark159] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure159 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem159] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance159 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent320 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent321 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction167(Jurisdiction123): - pass - - -class Disclosure160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction167] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy160 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem160] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark160] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure160 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem160] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance160 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent322 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent323 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction168(Jurisdiction123): - pass - - -class Disclosure161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction168] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy161 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem161] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark161] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure161 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem161] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target10 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target10.linear - provenance: Annotated[ - Provenance161 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets1221( - RootModel[ - Assets1222 - | Assets1223 - | Assets1224 - | Assets1225 - | Assets1226 - | Assets1227 - | Assets1228 - | Assets1229 - | Assets12210 - | Assets12211 - | Assets12212 - | Assets12213 - | Assets12214 - | Assets12215 - | Assets114 - | Assets12217 - | Assets12218 - | Assets12219 - | Assets12220 - | Assets12221 - | Assets12222 - | Assets12223 - ] -): - root: Annotated[ - Assets1222 - | Assets1223 - | Assets1224 - | Assets1225 - | Assets1226 - | Assets1227 - | Assets1228 - | Assets1229 - | Assets12210 - | Assets12211 - | Assets12212 - | Assets12213 - | Assets12214 - | Assets12215 - | Assets114 - | Assets12217 - | Assets12218 - | Assets12219 - | Assets12220 - | Assets12221 - | Assets12222 - | Assets12223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets122(RootModel[list[Assets1221]]): - root: Annotated[list[Assets1221], Field(min_length=1)] - - -class EmbeddedProvenanceItem162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent324 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent325 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction169(Jurisdiction123): - pass - - -class Disclosure162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction169] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy162 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem162] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark162] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure162 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem162] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance162 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent326 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent327 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction170(Jurisdiction123): - pass - - -class Disclosure163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction170] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy163 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem163] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark163] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure163 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem163] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: Annotated[ - FormatKind | None, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef5 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets101 - | Assets102 - | Assets103 - | Assets104 - | Assets105 - | Assets106 - | Assets107 - | Assets108 - | Assets109 - | Assets110 - | Assets111 - | Assets112 - | Assets113 - | Assets114 - | Assets115 - | Assets116 - | Assets117 - | Assets118 - | Assets119 - | Assets120 - | Assets121 - | Assets122, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance163 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent328 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent329 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction171(Jurisdiction123): - pass - - -class Disclosure164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction171] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy164 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem164] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark164] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure164 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem164] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance164 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent330 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent331 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction172(Jurisdiction123): - pass - - -class Disclosure165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction172] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy165 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem165] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark165] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure165 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem165] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance165 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent332 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent333 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction173(Jurisdiction123): - pass - - -class Disclosure166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction173] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy166 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem166] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark166] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure166 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem166] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance166 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent334 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent335 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction174(Jurisdiction123): - pass - - -class Disclosure167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction174] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy167 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem167] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark167] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure167 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem167] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance167 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent336 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent337 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction175(Jurisdiction123): - pass - - -class Disclosure168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction175] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy168 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem168] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark168] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure168 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem168] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance168 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent338 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent339 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction176(Jurisdiction123): - pass - - -class Disclosure169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction176] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy169 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem169] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark169] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure169 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem169] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance169 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent340 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent341 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction177(Jurisdiction123): - pass - - -class Disclosure170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction177] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy170 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem170] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark170] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure170 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem170] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance170 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent342 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent343 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction178(Jurisdiction123): - pass - - -class Disclosure171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction178] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy171 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem171] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark171] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure171 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem171] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance171 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent344 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent345 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction179(Jurisdiction123): - pass - - -class Disclosure172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction179] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy172 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem172] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark172] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure172 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem172] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance172 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent346 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent347 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction180(Jurisdiction123): - pass - - -class Disclosure173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction180] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy173 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem173] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark173] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure173 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem173] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance173 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent348 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent349 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction181(Jurisdiction123): - pass - - -class Disclosure174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction181] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy174 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem174] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark174] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure174 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem174] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance174 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent350 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent351 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction182(Jurisdiction123): - pass - - -class Disclosure175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction182] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy175 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem175] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark175] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure175 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem175] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance175 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent352 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent353 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction183(Jurisdiction123): - pass - - -class Disclosure176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction183] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy176 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem176] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark176] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure176 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem176] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance176 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent354 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent355 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction184(Jurisdiction123): - pass - - -class Disclosure177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction184] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy177 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem177] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark177] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure177 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem177] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance177 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets137(Assets114): - pass - - -class RequiredDisclosure7(RequiredDisclosure): - pass - - -class Compliance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure7] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets138(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset7] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance7 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets139(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping6] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent356 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent357 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction186(Jurisdiction123): - pass - - -class Disclosure178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction186] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy178 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem178] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark178] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure178 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem178] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization6 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance178 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent358 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent359 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction187(Jurisdiction123): - pass - - -class Disclosure179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction187] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy179 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem179] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark179] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure179 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem179] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance179 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent360 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent361 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction188(Jurisdiction123): - pass - - -class Disclosure180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction188] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy180 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem180] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark180] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure180 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem180] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance180 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent362 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent363 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction189(Jurisdiction123): - pass - - -class Disclosure181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction189] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy181 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem181] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark181] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure181 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem181] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance181 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent364 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent365 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction190(Jurisdiction123): - pass - - -class Disclosure182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction190] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy182 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem182] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark182] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure182 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem182] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media12 | Media13, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl6 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance182 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent366 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent367 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction191(Jurisdiction123): - pass - - -class Disclosure183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction191] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy183 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem183] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark183] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure183 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem183] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance183 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent368 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent369 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction192(Jurisdiction123): - pass - - -class Disclosure184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction192] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy184 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem184] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark184] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure184 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem184] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance184 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent370 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent371 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction193(Jurisdiction123): - pass - - -class Disclosure185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction193] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy185 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem185] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark185] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure185 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem185] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target10 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target10.linear - provenance: Annotated[ - Provenance185 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent372 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent373 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction194(Jurisdiction123): - pass - - -class Disclosure186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction194] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy186 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem186] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark186] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure186 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem186] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance186 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent374 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent375 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction195(Jurisdiction123): - pass - - -class Disclosure187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction195] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy187 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem187] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark187] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure187 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem187] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance187 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent376 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent377 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction196(Jurisdiction123): - pass - - -class Disclosure188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction196] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy188 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem188] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark188] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure188 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem188] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance188 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent378 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent379 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction197(Jurisdiction123): - pass - - -class Disclosure189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction197] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy189 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem189] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark189] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure189 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem189] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance189 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent380 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent381 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction198(Jurisdiction123): - pass - - -class Disclosure190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction198] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy190 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem190] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark190] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure190 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem190] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance190 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent382 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent383 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction199(Jurisdiction123): - pass - - -class Disclosure191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction199] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy191 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem191] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark191] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure191 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem191] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance191 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent384 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent385 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction200(Jurisdiction123): - pass - - -class Disclosure192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction200] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy192 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem192] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark192] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure192 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem192] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance192 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent386 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent387 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction201(Jurisdiction123): - pass - - -class Disclosure193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction201] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy193 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem193] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark193] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure193 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem193] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance193 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent388 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent389 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction202(Jurisdiction123): - pass - - -class Disclosure194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction202] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy194 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem194] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark194] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure194 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem194] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance194 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent390 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent391 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction203(Jurisdiction123): - pass - - -class Disclosure195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction203] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy195 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem195] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark195] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure195 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem195] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance195 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent392 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent393 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction204(Jurisdiction123): - pass - - -class Disclosure196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction204] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy196 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem196] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark196] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure196 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem196] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance196 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent394 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent395 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction205(Jurisdiction123): - pass - - -class Disclosure197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction205] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy197 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem197] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark197] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure197 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem197] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance197 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent396 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent397 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction206(Jurisdiction123): - pass - - -class Disclosure198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction206] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy198 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem198] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark198] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure198 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem198] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance198 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent398 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent399 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction207(Jurisdiction123): - pass - - -class Disclosure199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction207] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy199 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem199] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark199] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure199 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem199] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance199 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure8(RequiredDisclosure): - pass - - -class Compliance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure8] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets14517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset8] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance8 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets14518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping7] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent400 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent401 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction209(Jurisdiction123): - pass - - -class Disclosure200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction209] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy200 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem200] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark200] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure200 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem200] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization7 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance200 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent402 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent403 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction210(Jurisdiction123): - pass - - -class Disclosure201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction210] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy201 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem201] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark201] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure201 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem201] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance201 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent404 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent405 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction211(Jurisdiction123): - pass - - -class Disclosure202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction211] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy202 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem202] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark202] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure202 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem202] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance202 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent406 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent407 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction212(Jurisdiction123): - pass - - -class Disclosure203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction212] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy203 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem203] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark203] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure203 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem203] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance203 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent408 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent409 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction213(Jurisdiction123): - pass - - -class Disclosure204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction213] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy204 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem204] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark204] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure204 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem204] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media14 | Media15, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl7 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance204 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent410 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent411 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction214(Jurisdiction123): - pass - - -class Disclosure205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction214] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy205 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem205] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark205] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure205 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem205] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance205 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent412 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent413 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction215(Jurisdiction123): - pass - - -class Disclosure206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction215] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance206(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy206 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem206] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark206] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure206 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem206] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance206 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent414 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent415 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction216(Jurisdiction123): - pass - - -class Disclosure207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction216] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance207(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy207 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem207] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark207] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure207 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem207] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets14523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target10 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target10.linear - provenance: Annotated[ - Provenance207 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets1451( - RootModel[ - Assets1452 - | Assets1453 - | Assets1454 - | Assets1455 - | Assets1456 - | Assets1457 - | Assets1458 - | Assets1459 - | Assets14510 - | Assets14511 - | Assets14512 - | Assets14513 - | Assets14514 - | Assets14515 - | Assets114 - | Assets14517 - | Assets14518 - | Assets14519 - | Assets14520 - | Assets14521 - | Assets14522 - | Assets14523 - ] -): - root: Annotated[ - Assets1452 - | Assets1453 - | Assets1454 - | Assets1455 - | Assets1456 - | Assets1457 - | Assets1458 - | Assets1459 - | Assets14510 - | Assets14511 - | Assets14512 - | Assets14513 - | Assets14514 - | Assets14515 - | Assets114 - | Assets14517 - | Assets14518 - | Assets14519 - | Assets14520 - | Assets14521 - | Assets14522 - | Assets14523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets145(RootModel[list[Assets1451]]): - root: Annotated[list[Assets1451], Field(min_length=1)] - - -class EmbeddedProvenanceItem208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent416 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent417 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction217(Jurisdiction123): - pass - - -class Disclosure208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction217] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance208(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy208 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem208] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark208] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure208 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem208] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance208 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo15 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride15 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent418 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent419 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction218(Jurisdiction123): - pass - - -class Disclosure209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction218] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy209 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem209] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark209] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure209 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem209] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: Annotated[ - FormatKind, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef5 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets123 - | Assets124 - | Assets125 - | Assets126 - | Assets127 - | Assets128 - | Assets129 - | Assets130 - | Assets131 - | Assets132 - | Assets133 - | Assets134 - | Assets135 - | Assets136 - | Assets137 - | Assets138 - | Assets139 - | Assets140 - | Assets141 - | Assets142 - | Assets143 - | Assets144 - | Assets145, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand13 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right3] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier4] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance209 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent420 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent421 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction219(Jurisdiction123): - pass - - -class Disclosure210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction219] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy210 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem210] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark210] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure210 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem210] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance210 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo16 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride16 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand14, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class GetCreativeFeaturesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest | CreativeManifest4, - Field( - description='The creative manifest to evaluate. Contains format_id and assets.', - title='Creative Manifest', - ), - ] - feature_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to specific features. If omitted, returns all available features.', - min_length=1, - ), - ] = None - account: Annotated[ - Account | Account15 | None, - Field( - description='Account for billing this evaluation. Required when the governance agent charges per evaluation.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/get_creative_features_response.py b/src/adcp/types/generated_poc/bundled/creative/get_creative_features_response.py deleted file mode 100644 index 994351b5..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/get_creative_features_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/get_creative_features_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class GetCreativeFeaturesResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_request.py b/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_request.py deleted file mode 100644 index 35277671..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_request.py +++ /dev/null @@ -1,803 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_creative_formats_request.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Type(StrEnum): - audio = 'audio' - video = 'video' - display = 'display' - dooh = 'dooh' - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - url = 'url' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - - -class WcagLevel(StrEnum): - A = 'A' - AA = 'AA' - AAA = 'AAA' - - -class OutputFormatId(FormatId): - pass - - -class InputFormatId(FormatId): - pass - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1111(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account29(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class ListCreativeFormatsRequestCreativeAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field(description='Return only these specific format IDs', min_length=1), - ] = None - type: Annotated[ - Type | None, - Field( - description='Filter by format type (technical categories with distinct requirements)' - ), - ] = None - asset_types: Annotated[ - list[AssetType] | None, - Field( - description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", - min_length=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum width in pixels (inclusive). Returns formats with width <= this value. Omit for responsive/fluid formats.' - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum height in pixels (inclusive). Returns formats with height <= this value. Omit for responsive/fluid formats.' - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum width in pixels (inclusive). Returns formats with width >= this value.' - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum height in pixels (inclusive). Returns formats with height >= this value.' - ), - ] = None - is_responsive: Annotated[ - bool | None, - Field( - description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' - ), - ] = None - name_search: Annotated[ - str | None, Field(description='Search for formats by name (case-insensitive partial match)') - ] = None - wcag_level: Annotated[ - WcagLevel | None, - Field( - description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)', - title='WCAG Level', - ), - ] = None - disclosure_positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", - min_length=1, - ), - ] = None - disclosure_persistence: Annotated[ - list[DisclosurePersistence] | None, - Field( - description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `output_format_ids`) instead — build capability is a property of transformers, not a relationship between formats. *Legacy:* filter to formats whose `output_format_ids` includes any of these format IDs.', - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1.** Discover build capability via `list_transformers` (filter its `input_format_ids`) instead. *Legacy:* filter to formats whose `input_format_ids` includes any of these format IDs.', - min_length=1, - ), - ] = None - include_pricing: Annotated[ - bool | None, - Field( - description='Include pricing_options on each format. Used by transformation and generation agents that charge per format or per unit of work. Requires account. When false or omitted, pricing is not computed.' - ), - ] = False - account: Annotated[ - Account | Account29 | None, - Field( - description="Account reference for pricing. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each format.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py b/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py deleted file mode 100644 index 15a9cdb1..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_creative_formats_response.py +++ /dev/null @@ -1,6311 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_creative_formats_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class AcceptsParameter(StrEnum): - dimensions = 'dimensions' - duration = 'duration' - - -class Responsive(AdCPBaseModel): - width: bool - height: bool - - -class Format(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - avif = 'avif' - tiff = 'tiff' - pdf = 'pdf' - eps = 'eps' - - -class Bleed(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - uniform: Annotated[float, Field(description='Same bleed on all four sides', ge=0.0)] - - -class Bleed3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - top: Annotated[float, Field(ge=0.0)] - right: Annotated[float, Field(ge=0.0)] - bottom: Annotated[float, Field(ge=0.0)] - left: Annotated[float, Field(ge=0.0)] - - -class ColorSpace(StrEnum): - rgb = 'rgb' - cmyk = 'cmyk' - grayscale = 'grayscale' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - avi = 'avi' - mkv = 'mkv' - - -class Codec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class FrameRate(RootModel[float]): - root: Annotated[float, Field(ge=1.0)] - - -class AudioCodec(StrEnum): - aac = 'aac' - pcm = 'pcm' - ac3 = 'ac3' - eac3 = 'eac3' - mp3 = 'mp3' - opus = 'opus' - vorbis = 'vorbis' - flac = 'flac' - - -class AudioSampleRate(RootModel[int]): - root: Annotated[int, Field(ge=1)] - - -class Format20(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - ogg = 'ogg' - flac = 'flac' - - -class SampleRate(AudioSampleRate): - pass - - -class Channel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class Requirements2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_duration_ms: Annotated[ - int | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - int | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - formats: Annotated[list[Format20] | None, Field(description='Accepted audio file formats')] = ( - None - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - sample_rates: Annotated[ - list[SampleRate] | None, - Field(description='Accepted sample rates in Hz (e.g., [44100, 48000])'), - ] = None - channels: Annotated[ - list[Channel] | None, Field(description='Accepted audio channel configurations') - ] = None - min_bitrate_kbps: Annotated[ - int | None, Field(description='Minimum audio bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - int | None, Field(description='Maximum audio bitrate in kilobits per second', ge=1) - ] = None - - -class Requirements3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_length: Annotated[int | None, Field(description='Minimum character length', ge=0)] = None - max_length: Annotated[int | None, Field(description='Maximum character length', ge=1)] = None - min_lines: Annotated[int | None, Field(description='Minimum number of lines', ge=1)] = None - max_lines: Annotated[int | None, Field(description='Maximum number of lines', ge=1)] = None - character_pattern: Annotated[ - str | None, - Field( - description="Regex pattern defining allowed characters (e.g., '^[a-zA-Z0-9 .,!?-]+$')" - ), - ] = None - prohibited_terms: Annotated[ - list[str] | None, Field(description='List of prohibited words or phrases') - ] = None - allowed_values: Annotated[ - list[str] | None, - Field( - description='Closed set of permitted string values for this text slot. When present, a conformant implementation MUST reject any submitted content not in this list with `CREATIVE_VALUE_NOT_ALLOWED` (echoing the offending field path in `error.field` and the allowed list in `error.details.allowed_values`). Matching is case-sensitive; producers MUST supply exact casing. If the submitted value contains unresolved template tokens (e.g., {{product_name}}), validation against allowed_values MUST be deferred until interpolation is complete. All declared constraints (allowed_values, character_pattern, max_length, etc.) are conjunctive — submitted content must satisfy every applicable constraint simultaneously.', - min_length=1, - ), - ] = None - - -class Requirements4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_length: Annotated[int | None, Field(description='Maximum character length', ge=1)] = None - - -class Sandbox(StrEnum): - none = 'none' - iframe = 'iframe' - safeframe = 'safeframe' - fencedframe = 'fencedframe' - - -class Requirements5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes for the HTML asset', ge=1) - ] = None - sandbox: Annotated[ - Sandbox | None, - Field( - description="Sandbox environment the HTML must be compatible with. 'none' = direct DOM access, 'iframe' = standard iframe isolation, 'safeframe' = IAB SafeFrame container, 'fencedframe' = Privacy Sandbox fenced frame" - ), - ] = Sandbox.none - external_resources_allowed: Annotated[ - bool | None, - Field( - description='Whether the HTML creative can load external resources (scripts, images, fonts, etc.). When false, all resources must be inlined or bundled.' - ), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the HTML creative may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None - - -class Requirements6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - - -class ModuleType(StrEnum): - script = 'script' - module = 'module' - iife = 'iife' - - -class Requirements7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for the JavaScript asset', ge=1), - ] = None - module_type: Annotated[ - ModuleType | None, - Field( - description="Required JavaScript module format. 'script' = classic script, 'module' = ES modules, 'iife' = immediately invoked function expression" - ), - ] = None - strict_mode_required: Annotated[ - bool | None, Field(description='Whether the JavaScript must use strict mode') - ] = None - external_resources_allowed: Annotated[ - bool | None, - Field(description='Whether the JavaScript can load external resources dynamically'), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the JavaScript may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class Requirements8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version')] = None - - -class Requirements9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - daast_version: Annotated[ - Literal['1.0'] | None, - Field(description='Required DAAST version. DAAST 1.0 is the current IAB standard.'), - ] = None - - -class Role(StrEnum): - clickthrough = 'clickthrough' - landing_page = 'landing_page' - impression_tracker = 'impression_tracker' - click_tracker = 'click_tracker' - viewability_tracker = 'viewability_tracker' - third_party_tracker = 'third_party_tracker' - - -class Protocol(StrEnum): - https = 'https' - http = 'http' - - -class Requirements10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - Role | None, - Field( - description="Purpose this URL slot serves in the format — distinct from `url_type` on the manifest-side asset (which declares the receiver's invocation mechanism). A slot can be `click_tracker` (purpose) and accept a `tracker_pixel` (mechanism) URL, or `clickthrough` (purpose) and accept a `clickthrough` (mechanism) URL. Complements `asset_role` (human-readable label) by providing a machine-readable enum and serves as the receiver's fallback signal when a manifest URL asset omits `url_type`." - ), - ] = None - protocols: Annotated[ - list[Protocol] | None, - Field(description='Allowed URL protocols. HTTPS is recommended for all ad URLs.'), - ] = None - allowed_domains: Annotated[ - list[str] | None, Field(description='List of allowed domains for the URL') - ] = None - max_length: Annotated[ - int | None, Field(description='Maximum URL length in characters', ge=1) - ] = None - macro_support: Annotated[ - bool | None, - Field(description='Whether the URL supports macro substitution (e.g., ${CACHEBUSTER})'), - ] = None - - -class Method(StrEnum): - GET = 'GET' - POST = 'POST' - - -class Requirements11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - methods: Annotated[list[Method] | None, Field(description='Allowed HTTP methods')] = None - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - url = 'url' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - - -class Bleed4(Bleed): - pass - - -class Bleed5(Bleed3): - pass - - -class AssetRequirements(Requirements2): - pass - - -class AssetRequirements4(Requirements3): - pass - - -class AssetRequirements5(Requirements4): - pass - - -class AssetRequirements6(Requirements5): - pass - - -class AssetRequirements7(Requirements6): - pass - - -class AssetRequirements8(Requirements7): - pass - - -class AssetRequirements9(Requirements8): - pass - - -class AssetRequirements10(Requirements9): - pass - - -class AssetRequirements11(Requirements10): - pass - - -class AssetRequirements12(Requirements11): - pass - - -class SelectionMode(StrEnum): - sequential = 'sequential' - optimize = 'optimize' - - -class Bleed6(Bleed): - pass - - -class Bleed7(Bleed3): - pass - - -class Requirements15(Requirements2): - pass - - -class Requirements16(Requirements3): - pass - - -class Requirements17(Requirements4): - pass - - -class Requirements18(Requirements5): - pass - - -class Requirements19(Requirements6): - pass - - -class Requirements20(Requirements7): - pass - - -class Requirements21(Requirements8): - pass - - -class Requirements22(Requirements9): - pass - - -class Requirements23(Requirements10): - pass - - -class Requirements24(Requirements11): - pass - - -class SupportedMacros(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class InputFormatId(FormatId): - pass - - -class OutputFormatId(FormatId): - pass - - -class FormatCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description='Creative format defining the card layout (typically format_card_standard)', - title='Format Reference (Structured Object)', - ), - ] - manifest: Annotated[ - dict[str, Any], - Field(description='Asset manifest for rendering the card, structure defined by the format'), - ] - - -class WcagLevel(StrEnum): - A = 'A' - AA = 'AA' - AAA = 'AAA' - - -class Accessibility(AdCPBaseModel): - wcag_level: Annotated[ - WcagLevel, - Field( - description='WCAG conformance level that this format achieves. For format-rendered creatives, the format guarantees this level. For opaque creatives, the format requires assets that self-certify to this level.', - title='WCAG Level', - ), - ] - requires_accessible_assets: Annotated[ - bool | None, - Field( - description='When true, all assets with x-accessibility fields must include those fields. For inspectable assets (image, video, audio), this means providing accessibility metadata like alt_text or captions. For opaque assets (HTML, JavaScript), this means providing self-declared accessibility properties.' - ), - ] = None - - -class PersistenceEnum(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class FormatCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description='Creative format defining the detailed card layout (typically format_card_detailed)', - title='Format Reference (Structured Object)', - ), - ] - manifest: Annotated[ - dict[str, Any], - Field( - description='Asset manifest for rendering the detailed card, structure defined by the format' - ), - ] - - -class ReportedMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption191(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption196(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption197(PricingOption191, PricingOption196): - pass - - -class PricingOption198(PricingOption192, PricingOption196): - pass - - -class PricingOption199(PricingOption193, PricingOption196): - pass - - -class PricingOption1910(PricingOption194, PricingOption196): - pass - - -class PricingOption1911(PricingOption195, PricingOption196): - pass - - -class PricingOption( - RootModel[ - PricingOption197 - | PricingOption198 - | PricingOption199 - | PricingOption1910 - | PricingOption1911 - ] -): - root: Annotated[ - PricingOption197 - | PricingOption198 - | PricingOption199 - | PricingOption1910 - | PricingOption1911, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Kind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class SlotsOverrideItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Asset group identifier from `asset-group-vocabulary.json` (e.g., `generation_prompt`, `creative_brief`, `image_main`, `video_main`).' - ), - ] - asset_type: Annotated[ - str, - Field( - description='Asset type — `image`, `video`, `audio`, `text`, `html`, `javascript`, `url`, `zip`, `brief`, `catalog`, `published_post`, or another canonical slot asset type.' - ), - ] - required: Annotated[ - bool | None, Field(description='Whether the slot is required in the projected declaration.') - ] = False - max_chars: Annotated[ - int | None, Field(description='Max character count for text slots.', ge=1) - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="When false, slot is for moderation/review only and is NOT consumed by the seller's renderer (e.g., a brand-safety brief that informs review but doesn't appear in the rendered ad)." - ), - ] = True - - -class Canonical(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description='The v2 canonical-format-kind this v1 format projects to (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `sponsored_placement`, `responsive_creative`, `agent_placement`, or `custom`).', - title='Canonical Format Kind', - ), - ] - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from on the projected v2 declaration. Default (when omitted) is `buyer_uploaded` — the canonical's default. Set explicitly when the v1 named format doesn't follow that default. Required for generative entries (`agent_synthesized` or `seller_pre_rendered_from_brief`) because their asset shape doesn't carry image/video/audio bytes, and for published-post reference entries (`publisher_owned_reference`) because their asset shape carries a post reference rather than uploaded bytes. Projection without this hint produces a lossy v2 declaration that claims buyer-uploaded bytes." - ), - ] = None - slots_override: Annotated[ - list[SlotsOverrideItem] | None, - Field( - description="When the v1 named format's slot shape differs from the canonical's default slots, this carries the override that the projected v2 declaration's `params.slots[]` should use. REPLACES (does not merge with) the canonical's default slots — projection-time semantics. The slot vocabulary follows `asset-group-vocabulary.json`. Asset IDs in the v1 format's `assets[*]` MUST resolve (directly or via the vocabulary's aliases) to the `asset_group_id` values declared here.", - min_length=1, - ), - ] = None - - -class AppliesToChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class V1FormatRefItem(FormatId): - pass - - -class FormatSchema(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class PlatformExtension(FormatSchema): - pass - - -class AssetType111(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(RootModel[str]): - root: Annotated[ - str, - Field( - examples=[ - 'list_creatives', - 'sync_creatives', - 'create_media_buy', - 'get_media_buy_delivery', - 'get_creative_delivery', - ], - min_length=1, - ), - ] - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status159(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class RequiredConnection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status159 | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[int, Field(ge=1)] - height: Annotated[int, Field(ge=1)] - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RequiredConnection109(RequiredConnection): - pass - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class RequiredConnection110(RequiredConnection): - pass - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class RequiredConnection111(RequiredConnection): - pass - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class RequiredConnection112(RequiredConnection): - pass - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(RootModel[int]): - root: Annotated[int, Field(ge=0)] - - -class AudioCodec22(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container12(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(AudioSampleRate): - pass - - -class CompanionBannerHeight(AudioSampleRate): - pass - - -class RequiredConnection113(RequiredConnection): - pass - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class RequiredConnection114(RequiredConnection): - pass - - -class AudioCodec23(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class RequiredConnection115(RequiredConnection): - pass - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class RequiredConnection116(RequiredConnection): - pass - - -class SupportedCatalogType(StrEnum): - product = 'product' - store = 'store' - offering = 'offering' - hotel = 'hotel' - flight = 'flight' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - job = 'job' - inventory = 'inventory' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class RequiredConnection117(RequiredConnection): - pass - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat20(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource43(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class RequiredConnection118(RequiredConnection): - pass - - -class RequiredConnection119(RequiredConnection): - pass - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class CanonicalParameters12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class Capability(StrEnum): - validation = 'validation' - assembly = 'assembly' - generation = 'generation' - preview = 'preview' - delivery = 'delivery' - - -class CreativeAgent(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl, - Field( - description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." - ), - ] - agent_name: Annotated[ - str | None, Field(description='Human-readable name for the creative agent') - ] = None - capabilities: Annotated[ - list[Capability] | None, Field(description='Capabilities this creative agent provides') - ] = None - - -class Issue71(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue71] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class ScalarBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['scalar'] = 'scalar' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - catalog_field: Annotated[ - str, - Field( - description="Dot-notation path to the field on the catalog item (e.g., 'name', 'price.amount', 'location.city')." - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AssetPoolBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['asset_pool'] = 'asset_pool' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - asset_group_id: Annotated[ - str, - Field( - description="The asset_group_id on the catalog item's assets array to pull from (e.g., 'images_landscape', 'images_vertical', 'logo')." - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class DimensionUnit(StrEnum): - px = 'px' - dp = 'dp' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class Visual(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL to a theme-neutral overlay graphic (SVG or PNG). Use when a single file works for all backgrounds, e.g. an SVG using CSS custom properties or currentColor.' - ), - ] = None - light: Annotated[ - AnyUrl | None, - Field( - description='URL to the overlay graphic for use on light/bright backgrounds (SVG or PNG)' - ), - ] = None - dark: Annotated[ - AnyUrl | None, - Field(description='URL to the overlay graphic for use on dark backgrounds (SVG or PNG)'), - ] = None - - -class Unit(StrEnum): - px = 'px' - fraction = 'fraction' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' - - -class Bounds(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - x: Annotated[float, Field(description="Horizontal offset from the asset's left edge")] - y: Annotated[float, Field(description="Vertical offset from the asset's top edge")] - width: Annotated[float, Field(description='Width of the overlay', ge=0.0)] - height: Annotated[float, Field(description='Height of the overlay', ge=0.0)] - unit: Annotated[ - Unit, - Field( - description="'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (0.0 = edge, 1.0 = opposite edge). 'inches', 'cm', 'mm', 'pt' (1/72 inch) = physical units for print overlays, measured from asset top-left." - ), - ] - - -class Overlay(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable explanation of what this overlay is and how buyers should account for it' - ), - ] = None - visual: Annotated[ - Visual | None, - Field( - description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' - ), - ] = None - bounds: Annotated[ - Bounds, - Field( - description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." - ), - ] - - -class BaseGroupAsset(AdCPBaseModel): - asset_id: Annotated[str, Field(description='Identifier for this asset within the group')] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose. For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description='Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Same semantics as on baseIndividualAsset — lets buyers and migration tools resolve v1 author-invented slot names to canonical names.' - ), - ] = None - required: Annotated[ - bool, - Field(description='Whether this asset is required within each repetition of the group'), - ] - overlays: Annotated[ - list[Overlay] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., carousel navigation arrows, slide indicators). Creative agents should avoid placing critical content within overlay bounds." - ), - ] = None - - -class Bounds1(Bounds): - pass - - -class Overlay2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable explanation of what this overlay is and how buyers should account for it' - ), - ] = None - visual: Annotated[ - Visual | None, - Field( - description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' - ), - ] = None - bounds: Annotated[ - Bounds1, - Field( - description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." - ), - ] - - -class BaseIndividualAsset(AdCPBaseModel): - item_type: Annotated[ - Literal['individual'], - Field(description='Discriminator indicating this is an individual asset'), - ] = 'individual' - asset_id: Annotated[ - str, - Field( - description='Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object.' - ), - ] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - required: Annotated[ - bool, - Field( - description='Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.' - ), - ] - overlays: Annotated[ - list[Overlay2] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., video player controls, publisher logos). Creative agents should avoid placing critical content (CTAs, logos, key copy) within overlay bounds." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Lets buyers and migration tools resolve v1 author-invented slot names (e.g., `click_url`) to canonical names (e.g., `landing_page_url`). Validators MAY soft-warn when a v1 slot's asset_id is a known alias but no asset_group_id is declared." - ), - ] = None - - -class Dimensions(AdCPBaseModel): - width: Annotated[ - float | None, - Field(description='Fixed width. Interpretation depends on unit (default: pixels).', gt=0.0), - ] = None - height: Annotated[ - float | None, - Field( - description='Fixed height. Interpretation depends on unit (default: pixels).', gt=0.0 - ), - ] = None - min_width: Annotated[ - float | None, Field(description='Minimum width for responsive renders', gt=0.0) - ] = None - min_height: Annotated[ - float | None, Field(description='Minimum height for responsive renders', gt=0.0) - ] = None - max_width: Annotated[ - float | None, Field(description='Maximum width for responsive renders', gt=0.0) - ] = None - max_height: Annotated[ - float | None, Field(description='Maximum height for responsive renders', gt=0.0) - ] = None - unit: DimensionUnit | None = None - responsive: Annotated[ - Responsive | None, Field(description='Indicates which dimensions are responsive/fluid') - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - - -class Renders(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - bool | None, - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] = None - dimensions: Annotated[ - Dimensions, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] - - -class Dimensions57(Dimensions): - pass - - -class Renders11(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - Literal[True], - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] - dimensions: Annotated[ - Dimensions57 | None, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] = None - - -class Requirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format] | None, Field(description='Accepted image file formats')] = None - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed | Bleed3 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class Assets(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['image'] = 'image' - requirements: Annotated[ - Requirements | None, - Field( - description='Requirements for image creative assets. These define the technical constraints for image files.', - title='Image Asset Requirements', - ), - ] = None - - -class Requirements1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[int | None, Field(description='Minimum width in pixels', ge=1)] = None - max_width: Annotated[int | None, Field(description='Maximum width in pixels', ge=1)] = None - min_height: Annotated[int | None, Field(description='Minimum height in pixels', ge=1)] = None - max_height: Annotated[int | None, Field(description='Maximum height in pixels', ge=1)] = None - aspect_ratio: Annotated[ - str | None, - Field(description="Required aspect ratio (e.g., '16:9', '9:16')", pattern='^\\d+:\\d+$'), - ] = None - min_duration_ms: Annotated[ - int | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - int | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - containers: Annotated[ - list[Container] | None, Field(description='Accepted video container formats') - ] = None - codecs: Annotated[list[Codec] | None, Field(description='Accepted video codecs')] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - min_bitrate_kbps: Annotated[ - int | None, Field(description='Minimum video bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - int | None, Field(description='Maximum video bitrate in kilobits per second', ge=1) - ] = None - frame_rates: Annotated[ - list[FrameRate] | None, - Field(description='Accepted frame rates in frames per second (e.g., [24, 30, 60])'), - ] = None - audio_required: Annotated[ - bool | None, Field(description='Whether the video must include an audio track') - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - gop_type: GOPType | None = None - min_gop_interval_seconds: Annotated[ - float | None, Field(description='Minimum keyframe interval in seconds', ge=0.0) - ] = None - max_gop_interval_seconds: Annotated[ - float | None, - Field( - description='Maximum keyframe interval in seconds. SSAI typically requires 1-2 second intervals.', - ge=0.0, - ), - ] = None - moov_atom_position: MoovAtomPosition | None = None - audio_codecs: Annotated[ - list[AudioCodec] | None, - Field(description="Accepted audio codecs (e.g., ['aac', 'pcm', 'ac3'])"), - ] = None - audio_sample_rates: Annotated[ - list[AudioSampleRate] | None, - Field(description='Accepted audio sample rates in Hz (e.g., [44100, 48000])'), - ] = None - audio_channels: Annotated[ - list[AudioChannelLayout] | None, Field(description='Accepted audio channel configurations') - ] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Target integrated loudness in LUFS (e.g., -24 for broadcast, -16 for streaming)' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, - Field( - description='Acceptable deviation from loudness_lufs target in dB (e.g., 2 means -22 to -26 LUFS for a -24 target)', - ge=0.0, - ), - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true peak level in dBFS (e.g., -2 for broadcast)') - ] = None - - -class Assets285(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['video'] = 'video' - requirements: Annotated[ - Requirements1 | None, - Field( - description='Requirements for video creative assets. These define the technical constraints for video files.', - title='Video Asset Requirements', - ), - ] = None - - -class Assets286(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['audio'] = 'audio' - requirements: Annotated[ - Requirements2 | None, - Field( - description='Requirements for audio creative assets.', title='Audio Asset Requirements' - ), - ] = None - - -class Assets287(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['text'] = 'text' - requirements: Annotated[ - Requirements3 | None, - Field( - description='Requirements for text creative assets such as headlines, body copy, and CTAs.', - title='Text Asset Requirements', - ), - ] = None - - -class Assets288(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['markdown'] = 'markdown' - requirements: Annotated[ - Requirements4 | None, - Field( - description='Requirements for markdown creative assets.', - title='Markdown Asset Requirements', - ), - ] = None - - -class Assets289(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['html'] = 'html' - requirements: Annotated[ - Requirements5 | None, - Field( - description='Requirements for HTML creative assets. These define the execution environment constraints that the HTML must be compatible with.', - title='HTML Asset Requirements', - ), - ] = None - - -class Assets290(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['css'] = 'css' - requirements: Annotated[ - Requirements6 | None, - Field(description='Requirements for CSS creative assets.', title='CSS Asset Requirements'), - ] = None - - -class Assets291(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['javascript'] = 'javascript' - requirements: Annotated[ - Requirements7 | None, - Field( - description='Requirements for JavaScript creative assets. These define the execution environment constraints that the JavaScript must be compatible with.', - title='JavaScript Asset Requirements', - ), - ] = None - - -class Assets292(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['zip'] = 'zip' - - -class Assets293(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['vast'] = 'vast' - requirements: Annotated[ - Requirements8 | None, - Field( - description='Requirements for VAST (Video Ad Serving Template) creative assets.', - title='VAST Asset Requirements', - ), - ] = None - - -class Assets294(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['daast'] = 'daast' - requirements: Annotated[ - Requirements9 | None, - Field( - description='Requirements for DAAST (Digital Audio Ad Serving Template) creative assets.', - title='DAAST Asset Requirements', - ), - ] = None - - -class Assets295(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['url'] = 'url' - requirements: Annotated[ - Requirements10 | None, - Field( - description='Requirements for URL assets such as click-through URLs, tracking pixels, and landing pages.', - title='URL Asset Requirements', - ), - ] = None - - -class Assets296(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['webhook'] = 'webhook' - requirements: Annotated[ - Requirements11 | None, - Field( - description='Requirements for webhook creative assets.', - title='Webhook Asset Requirements', - ), - ] = None - - -class Assets297(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['brief'] = 'brief' - - -class AssetRequirements1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format] | None, Field(description='Accepted image file formats')] = None - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed4 | Bleed5 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class AssetRequirements2(Requirements1): - pass - - -class OfferingAssetConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description="The asset group this constraint applies to. Values are format-defined vocabulary — each format chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them via list_creative_formats." - ), - ] - asset_type: Annotated[ - AssetType, - Field(description='The expected content type for this group.', title='Asset Content Type'), - ] - required: Annotated[ - bool | None, - Field( - description='Whether this asset group must be present in each offering. Defaults to true.' - ), - ] = True - min_count: Annotated[ - int | None, Field(description='Minimum number of items required in this group.', ge=1) - ] = None - max_count: Annotated[ - int | None, Field(description='Maximum number of items allowed in this group.', ge=1) - ] = None - asset_requirements: Annotated[ - AssetRequirements1 - | AssetRequirements2 - | AssetRequirements - | AssetRequirements4 - | AssetRequirements5 - | AssetRequirements6 - | AssetRequirements7 - | AssetRequirements8 - | AssetRequirements9 - | AssetRequirements10 - | AssetRequirements11 - | AssetRequirements12 - | None, - Field( - description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.', - title='Asset Requirements', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PerItemBindings(RootModel[ScalarBinding | AssetPoolBinding]): - root: Annotated[ScalarBinding | AssetPoolBinding, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FieldBindings1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['catalog_group'] = 'catalog_group' - format_group_id: Annotated[ - str, - Field(description="The asset_group_id of a repeatable_group in the format's assets array."), - ] - catalog_item: Annotated[ - Literal[True], - Field( - description="Each repetition of the format's repeatable_group maps to one item from the catalog." - ), - ] - per_item_bindings: Annotated[ - list[PerItemBindings] | None, - Field( - description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FieldBindings(RootModel[ScalarBinding | AssetPoolBinding | FieldBindings1]): - root: Annotated[ - ScalarBinding | AssetPoolBinding | FieldBindings1, - Field( - description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", - discriminator='kind', - examples=[ - { - 'description': 'Scalar binding — hotel name to headline slot', - 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, - }, - { - 'description': 'Scalar binding — nested field (nightly rate)', - 'data': { - 'kind': 'scalar', - 'asset_id': 'price_badge', - 'catalog_field': 'price.amount', - }, - }, - { - 'description': 'Asset pool binding — hero image from landscape pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'hero_image', - 'asset_group_id': 'images_landscape', - }, - }, - { - 'description': 'Asset pool binding — Snap vertical background from vertical pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'snap_background', - 'asset_group_id': 'images_vertical', - }, - }, - { - 'description': 'Catalog group binding — carousel where each slide is one hotel', - 'data': { - 'kind': 'catalog_group', - 'format_group_id': 'slide', - 'catalog_item': True, - 'per_item_bindings': [ - {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, - { - 'kind': 'scalar', - 'asset_id': 'price', - 'catalog_field': 'price.amount', - }, - { - 'kind': 'asset_pool', - 'asset_id': 'image', - 'asset_group_id': 'images_landscape', - }, - ], - }, - }, - ], - title='Catalog Field Binding', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Requirements12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_type: Annotated[ - CatalogType, - Field(description='The catalog type this requirement applies to', title='Catalog Type'), - ] - required: Annotated[ - bool | None, - Field( - description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' - ), - ] = True - min_items: Annotated[ - int | None, - Field( - description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)', - ge=1, - ), - ] = None - max_items: Annotated[ - int | None, - Field( - description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.', - ge=1, - ), - ] = None - required_fields: Annotated[ - list[str] | None, - Field( - description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).", - min_length=1, - ), - ] = None - feed_formats: Annotated[ - list[FeedFormat] | None, - Field( - description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.', - min_length=1, - ), - ] = None - offering_asset_constraints: Annotated[ - list[OfferingAssetConstraint] | None, - Field( - description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.", - min_length=1, - ), - ] = None - field_bindings: Annotated[ - list[FieldBindings] | None, - Field( - description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.', - min_length=1, - ), - ] = None - - -class Assets298(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['catalog'] = 'catalog' - requirements: Annotated[ - Requirements12 | None, - Field( - description='Format-level declaration of what catalog feeds a creative needs. Formats that render product listings, store locators, or promotional content declare which catalog types must be synced and what fields each catalog must provide. Buyers use this to ensure the right catalogs are synced before submitting creatives.', - title='Catalog Requirements', - ), - ] = None - - -class Requirements13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format] | None, Field(description='Accepted image file formats')] = None - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed6 | Bleed7 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class Assets301(BaseGroupAsset): - asset_type: Literal['image'] = 'image' - requirements: Annotated[ - Requirements13 | None, - Field( - description='Requirements for image creative assets. These define the technical constraints for image files.', - title='Image Asset Requirements', - ), - ] = None - - -class Requirements14(Requirements1): - pass - - -class Assets302(BaseGroupAsset): - asset_type: Literal['video'] = 'video' - requirements: Annotated[ - Requirements14 | None, - Field( - description='Requirements for video creative assets. These define the technical constraints for video files.', - title='Video Asset Requirements', - ), - ] = None - - -class Assets303(BaseGroupAsset): - asset_type: Literal['audio'] = 'audio' - requirements: Annotated[ - Requirements15 | None, - Field( - description='Requirements for audio creative assets.', title='Audio Asset Requirements' - ), - ] = None - - -class Assets304(BaseGroupAsset): - asset_type: Literal['text'] = 'text' - requirements: Annotated[ - Requirements16 | None, - Field( - description='Requirements for text creative assets such as headlines, body copy, and CTAs.', - title='Text Asset Requirements', - ), - ] = None - - -class Assets305(BaseGroupAsset): - asset_type: Literal['markdown'] = 'markdown' - requirements: Annotated[ - Requirements17 | None, - Field( - description='Requirements for markdown creative assets.', - title='Markdown Asset Requirements', - ), - ] = None - - -class Assets306(BaseGroupAsset): - asset_type: Literal['html'] = 'html' - requirements: Annotated[ - Requirements18 | None, - Field( - description='Requirements for HTML creative assets. These define the execution environment constraints that the HTML must be compatible with.', - title='HTML Asset Requirements', - ), - ] = None - - -class Assets307(BaseGroupAsset): - asset_type: Literal['css'] = 'css' - requirements: Annotated[ - Requirements19 | None, - Field(description='Requirements for CSS creative assets.', title='CSS Asset Requirements'), - ] = None - - -class Assets308(BaseGroupAsset): - asset_type: Literal['javascript'] = 'javascript' - requirements: Annotated[ - Requirements20 | None, - Field( - description='Requirements for JavaScript creative assets. These define the execution environment constraints that the JavaScript must be compatible with.', - title='JavaScript Asset Requirements', - ), - ] = None - - -class Assets309(BaseGroupAsset): - asset_type: Literal['zip'] = 'zip' - - -class Assets310(BaseGroupAsset): - asset_type: Literal['vast'] = 'vast' - requirements: Annotated[ - Requirements21 | None, - Field( - description='Requirements for VAST (Video Ad Serving Template) creative assets.', - title='VAST Asset Requirements', - ), - ] = None - - -class Assets311(BaseGroupAsset): - asset_type: Literal['daast'] = 'daast' - requirements: Annotated[ - Requirements22 | None, - Field( - description='Requirements for DAAST (Digital Audio Ad Serving Template) creative assets.', - title='DAAST Asset Requirements', - ), - ] = None - - -class Assets312(BaseGroupAsset): - asset_type: Literal['url'] = 'url' - requirements: Annotated[ - Requirements23 | None, - Field( - description='Requirements for URL assets such as click-through URLs, tracking pixels, and landing pages.', - title='URL Asset Requirements', - ), - ] = None - - -class Assets313(BaseGroupAsset): - asset_type: Literal['webhook'] = 'webhook' - requirements: Annotated[ - Requirements24 | None, - Field( - description='Requirements for webhook creative assets.', - title='Webhook Asset Requirements', - ), - ] = None - - -class Assets300( - RootModel[ - Assets301 - | Assets302 - | Assets303 - | Assets304 - | Assets305 - | Assets306 - | Assets307 - | Assets308 - | Assets309 - | Assets310 - | Assets311 - | Assets312 - | Assets313 - ] -): - root: Annotated[ - Assets301 - | Assets302 - | Assets303 - | Assets304 - | Assets305 - | Assets306 - | Assets307 - | Assets308 - | Assets309 - | Assets310 - | Assets311 - | Assets312 - | Assets313, - Field(discriminator='asset_type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets299(AdCPBaseModel): - item_type: Annotated[ - Literal['repeatable_group'], - Field(description='Discriminator indicating this is a repeatable asset group'), - ] = 'repeatable_group' - asset_group_id: Annotated[ - str, Field(description="Identifier for this asset group (e.g., 'product', 'slide', 'card')") - ] - required: Annotated[ - bool, - Field( - description='Whether this asset group is required. If true, at least min_count repetitions must be provided.' - ), - ] - min_count: Annotated[ - int, - Field( - description='Minimum number of repetitions required (if group is required) or allowed (if optional)', - ge=0, - ), - ] - max_count: Annotated[int, Field(description='Maximum number of repetitions allowed', ge=1)] - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="How the platform uses repetitions of this group. 'sequential' means all items display in order (carousels, playlists). 'optimize' means the platform selects the best-performing combination from alternatives (asset group optimization like Meta Advantage+ or Google Pmax)." - ), - ] = SelectionMode.sequential - assets: Annotated[ - list[Assets300], Field(description='Assets within each repetition of this group') - ] - - -class DisclosureCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - position: DisclosurePosition - persistence: Annotated[ - list[PersistenceEnum], - Field(description='Persistence modes this position supports', min_length=1), - ] - - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType111, - Field( - description="Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." - ), - ] - required: Annotated[ - bool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - int | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - int | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - int | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - int | None, - Field( - description="Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot110(Slot): - pass - - -class Params110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot110] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection109] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params110, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot111(Slot): - pass - - -class Params111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot111] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection110] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class CanonicalParameters2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params111, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot112(Slot): - pass - - -class Params112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot112] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection111] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params112, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot113(Slot): - pass - - -class Params113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot113] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection112] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[Codec] | None = None - audio_codecs: list[AudioCodec22] | None = None - containers: list[Container12] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params113, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot114(Slot): - pass - - -class Params114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot114] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection113] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params114, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot115(Slot): - pass - - -class Params115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot115] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection114] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec23] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[Channel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class CanonicalParameters6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params115, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot116(Slot): - pass - - -class Params116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot116] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection115] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class CanonicalParameters7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params116, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot117(Slot): - pass - - -class Params117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot117] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection116] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class CanonicalParameters8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params117, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot118(Slot): - pass - - -class Params118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot118] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection117] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat20] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource43 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource43.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params118, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot119(Slot): - pass - - -class Params119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot119] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection118] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class CanonicalParameters10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params119, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot120(Slot): - pass - - -class Params120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot120] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection119] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class CanonicalParameters11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params120, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class Format18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="This format's own identifier — a structured object {agent_url, id}, not a string. See /schemas/core/format-id.json for the full shape.", - title='Format Reference (Structured Object)', - ), - ] - name: Annotated[str, Field(description='Human-readable format name')] - description: Annotated[ - str | None, - Field( - description='Plain text explanation of what this format does and what assets it requires' - ), - ] = None - example_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to showcase page with examples and interactive demos of this format' - ), - ] = None - accepts_parameters: Annotated[ - list[AcceptsParameter] | None, - Field( - description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.' - ), - ] = None - renders: Annotated[ - list[Renders | Renders11] | None, - Field( - description='Specification of rendered pieces for this format. Most formats produce a single render. Companion ad formats (video + banner), adaptive formats, and multi-placement formats produce multiple renders. Each render specifies its role and dimensions.', - min_length=1, - ), - ] = None - assets: Annotated[ - list[ - Assets - | Assets285 - | Assets286 - | Assets287 - | Assets288 - | Assets289 - | Assets290 - | Assets291 - | Assets292 - | Assets293 - | Assets294 - | Assets295 - | Assets296 - | Assets297 - | Assets298 - | Assets299 - ] - | None, - Field( - description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory." - ), - ] = None - delivery: Annotated[ - dict[str, Any] | None, - Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'), - ] = None - supported_macros: Annotated[ - list[SupportedMacros | str] | None, - Field( - description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.' - ), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `input_format_ids`/`output_format_ids`, so build capability is a property of the transformer (the unit you select and that carries pricing), not a relationship hung on a format. Discover build capability via `list_transformers` (optionally filtered by `input_format_ids`/`output_format_ids`).\n\nMigration: sellers that expressed transform capability by hanging `input_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead. Buyers SHOULD discover build capability via `list_transformers` rather than filtering formats.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format accepts as input creative manifests; when present, indicates this format can take existing creatives in these formats as input. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `output_format_ids`, so what a builder can produce is a property of the transformer, not a relationship hung on a format. Discover via `list_transformers`.\n\nMigration: sellers that expressed multi-output build capability (e.g. a multi-publisher template) by hanging `output_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format can produce as output; when present, indicates this format can build creatives in these output formats. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - format_card: Annotated[ - FormatCard | None, - Field( - description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.' - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field( - description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.' - ), - ] = None - supported_disclosure_positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.', - min_length=1, - ), - ] = None - disclosure_capabilities: Annotated[ - list[DisclosureCapability] | None, - Field( - description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.', - min_length=1, - ), - ] = None - format_card_detailed: Annotated[ - FormatCardDetailed | None, - Field( - description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.' - ), - ] = None - reported_metrics: Annotated[ - list[ReportedMetric] | None, - Field( - description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. If omitted, the format defers entirely to product-level metric declarations.', - min_length=1, - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `transformer.pricing_options` (via `list_transformers`) instead — pricing belongs on the transformer (the unit selected and billed), exactly as it belongs on a media-buy product. Once formats only describe output shape, format-level pricing is vestigial.\n\nMigration: transformation/generation agents that charged via `format.pricing_options` SHOULD move the same `vendor-pricing-option` entries onto the corresponding transformer. The applied option is echoed per-leaf on the build_creative response and reconciled via report_usage, unchanged.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* pricing options for this format, used by transformation/generation agents that charge per format adapted, per image generated, or per unit of work; present when the request included include_pricing=true and account. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - min_length=1, - ), - ] = None - canonical: Annotated[ - Canonical | None, - Field( - description='Optional v2 canonical-projection annotation. Always an object — bare-string shorthand (`canonical: "image"`) is not supported; the minimal form is `canonical: { "kind": "image" }`. Carries `kind` (which canonical the v1 format projects to) plus optional `asset_source` and `slots_override` for cases where the v1 format\'s shape doesn\'t follow the canonical\'s defaults (e.g., generative entries whose input is `generation_prompt: text` instead of `image_main: image`).\n\nWhen set, SDKs use this annotation as the authoritative v1 → v2 mapping for this format, bypassing the [v1 canonical mapping registry](/schemas/registries/v1-canonical-mapping.json) lookup. Combined with the slot-level `asset_group_id` declarations on each `assets[i]` entry, a v1 format declaration with `canonical` set is fully self-describing for v1↔v2 translation.\n\nResolution order for SDK projection from v1 wire shape to v2 (per RFC #3305 amendment #3767):\n1. If this `canonical` field is set, use it (seller-declared, highest priority). Apply `asset_source` and `slots_override` from the projection ref when present; otherwise inherit the canonical\'s defaults.\n2. Else, look up `format_id` in the canonical mapping registry\'s `format_id_glob` entries.\n3. Else, attempt structural match against the registry\'s `structural` entries (asset types, slot shape, vast_versions, etc.).\n4. Else, fail closed: SDK MUST NOT emit `format_options` for products carrying this format. Surface `FORMAT_PROJECTION_FAILED` on the response `errors[]` suggesting the seller add an explicit `canonical` annotation or file a registry entry.\n\nWhen `canonical.kind` is `custom`, the seller MUST also declare `canonical_format_shape` and `canonical_format_schema` (parallel to ProductFormatDeclaration\'s `format_shape` and `format_schema`) so buyer SDKs can fetch the seller\'s custom format schema.\n\nSee `canonical-projection-ref.json` for full projection semantics and examples (default-slot case, generative case, brief-driven case).', - examples=[ - {'kind': 'image'}, - { - 'kind': 'image', - 'asset_source': 'agent_synthesized', - 'slots_override': [ - { - 'asset_group_id': 'generation_prompt', - 'asset_type': 'text', - 'required': True, - } - ], - }, - { - 'kind': 'video_hosted', - 'asset_source': 'seller_pre_rendered_from_brief', - 'slots_override': [ - { - 'asset_group_id': 'creative_brief', - 'asset_type': 'brief', - 'required': True, - } - ], - }, - ], - title='Canonical Projection Reference', - ), - ] = None - canonical_parameters: Annotated[ - CanonicalParameters - | CanonicalParameters1 - | CanonicalParameters2 - | CanonicalParameters3 - | CanonicalParameters4 - | CanonicalParameters5 - | CanonicalParameters6 - | CanonicalParameters7 - | CanonicalParameters8 - | CanonicalParameters9 - | CanonicalParameters10 - | CanonicalParameters11 - | CanonicalParameters12 - | None, - Field( - deprecated=True, - description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] = None - - -class ListCreativeFormatsResponseCreativeAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - formats: Annotated[ - list[Format18], - Field( - description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field." - ), - ] - creative_agents: Annotated[ - list[CreativeAgent] | None, - Field( - description='Optional: Creative agents that provide additional formats. Buyers can recursively query these agents to discover more formats. No authentication required for list_creative_formats.' - ), - ] = None - errors: Annotated[ - list[Error] | None, Field(description='Task-specific errors and warnings') - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_creatives_request.py b/src/adcp/types/generated_poc/bundled/creative/list_creatives_request.py deleted file mode 100644 index aeb23727..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_creatives_request.py +++ /dev/null @@ -1,1141 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_creatives_request.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Accounts(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1113(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Status(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Field1(StrEnum): - created_date = 'created_date' - updated_date = 'updated_date' - name = 'name' - status = 'status' - assignment_count = 'assignment_count' - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[ - Field1 | None, Field(description='Field to sort by', title='Creative Sort Field') - ] = Field1.created_date - direction: Annotated[ - Direction | None, Field(description='Sort direction', title='Sort Direction') - ] = Direction.desc - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class Account(Accounts): - pass - - -class DeclaredBy557(DeclaredBy): - pass - - -class VerifyAgent1114(VerifyAgent): - pass - - -class VerifyAgent1115(VerifyAgent1113): - pass - - -class VerificationItem557(VerificationItem): - pass - - -class Field6(StrEnum): - creative_id = 'creative_id' - name = 'name' - format_id = 'format_id' - status = 'status' - created_date = 'created_date' - updated_date = 'updated_date' - tags = 'tags' - assignments = 'assignments' - snapshot = 'snapshot' - items = 'items' - variables = 'variables' - concept = 'concept' - pricing_options = 'pricing_options' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Accounts1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - accounts: Annotated[ - list[Accounts | Accounts1] | None, - Field( - description='Filter creatives by owning accounts. Useful for agencies managing multiple client accounts.', - min_length=1, - ), - ] = None - statuses: Annotated[ - list[Status] | None, Field(description='Filter by creative approval statuses', min_length=1) - ] = None - tags: Annotated[ - list[str] | None, - Field(description='Filter by creative tags (all tags must match)', min_length=1), - ] = None - tags_any: Annotated[ - list[str] | None, - Field(description='Filter by creative tags (any tag must match)', min_length=1), - ] = None - name_contains: Annotated[ - str | None, - Field(description='Filter by creative names containing this text (case-insensitive)'), - ] = None - creative_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific creative IDs', max_length=100, min_length=1), - ] = None - created_after: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives created after this date (ISO 8601)'), - ] = None - created_before: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives created before this date (ISO 8601)'), - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter creatives last updated before this date (ISO 8601)'), - ] = None - assigned_to_packages: Annotated[ - list[str] | None, - Field( - description='Filter creatives assigned to any of these packages. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', - min_length=1, - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Filter creatives assigned to any of these media buys. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.', - min_length=1, - ), - ] = None - unassigned: Annotated[ - bool | None, - Field( - description='Filter for unassigned creatives when true, assigned creatives when false. Sales-agent-specific — standalone creative agents SHOULD ignore this filter.' - ), - ] = None - has_served: Annotated[ - bool | None, - Field( - description='When true, return only creatives that have served at least one impression. When false, return only creatives that have never served.' - ), - ] = None - concept_ids: Annotated[ - list[str] | None, - Field( - description='Filter by creative concept IDs. Concepts group related creatives across sizes and formats (e.g., Flashtalking concepts, Celtra campaign folders, CM360 creative groups).', - min_length=1, - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Filter by structured format IDs. Returns creatives that match any of these formats.', - min_length=1, - ), - ] = None - has_variables: Annotated[ - bool | None, - Field( - description='When true, return only creatives with dynamic variables (DCO). When false, return only static creatives.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Vendor-namespaced extension parameters for seller- or platform-specific creative filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem557(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark557(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction580(Jurisdiction): - pass - - -class Disclosure559(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction580] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance557(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy557 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem557] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark557] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure559 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem557] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance557 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo81 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand31(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride81 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account31(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand31, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class ListCreativesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description="Filter criteria for querying creatives from a creative library. By default, archived creatives are excluded from results. To include archived creatives, explicitly filter by status='archived' or include 'archived' in the statuses array.", - title='Creative Filters', - ), - ] = None - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - include_assignments: Annotated[ - bool | None, Field(description='Include package assignment information in response') - ] = True - include_snapshot: Annotated[ - bool | None, - Field( - description='Include a lightweight delivery snapshot per creative (lifetime impressions and last-served date). For detailed performance analytics, use get_creative_delivery.' - ), - ] = False - include_items: Annotated[ - bool | None, - Field(description='Include items for multi-asset formats like carousels and native ads'), - ] = False - include_variables: Annotated[ - bool | None, - Field( - description='Include dynamic content variable definitions (DCO slots) for each creative' - ), - ] = False - include_pricing: Annotated[ - bool | None, - Field( - description='Include pricing_options on each creative. Requires account to be provided. When false or omitted, pricing is not computed.' - ), - ] = False - include_purged: Annotated[ - bool | None, - Field( - description="Include soft-purged creative tombstones in the result set. When true, creatives destroyed via `creative.purged` with `purge_kind: soft` surface as tombstone records carrying `purged: true`, `purged_at`, and the purge reason — within the seller's webhook activity retention window (30 days from `purged_at`, MUST match `webhook-activity-record` retention). Hard-purged creatives MUST NOT appear regardless of this flag. When false or omitted, the result set excludes all purged creatives — same default as today." - ), - ] = False - include_webhook_activity: Annotated[ - bool | None, - Field( - description='Include recent webhook activity per creative. When true, each returned creative carries a `webhook_activity[]` array of the most recent fires scoped to that creative — `creative.status_changed` and `creative.purged` deliveries. Adoption of the `webhook_activity[]` pattern per `snapshot-and-log.mdx § Webhook activity log pattern`. Retention is 30 days from `completed_at` (MUST). Three-state presence applies: omitted = seller does not surface; `[]` = persists but no recent fires; non-empty = actual records.' - ), - ] = False - webhook_activity_limit: Annotated[ - int | None, - Field( - description="Maximum number of `webhook_activity[]` records to return per creative. Only meaningful when `include_webhook_activity: true`. Sellers MUST respect the cap; structural enforcement is provided by the response schema's `maxItems: 200` on the array.", - ge=1, - le=200, - ), - ] = 50 - account: Annotated[ - Account | Account31 | None, - Field( - description="Account reference for pricing and access. When provided with include_pricing, the agent returns pricing_options from this account's rate card on each creative.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - fields: Annotated[ - list[Field6] | None, - Field( - description="Specific fields to include in response (omit for all fields). The 'concept' value returns both concept_id and concept_name.", - min_length=1, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_creatives_response.py b/src/adcp/types/generated_poc/bundled/creative/list_creatives_response.py deleted file mode 100644 index 623b252a..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_creatives_response.py +++ /dev/null @@ -1,12281 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_creatives_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class SortApplied(AdCPBaseModel): - field: str | None = None - direction: Annotated[ - Direction | None, - Field(description='Sort direction for list queries', title='Sort Direction'), - ] = None - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - int, - Field(description='Total number of creatives matching filters (across all pages)', ge=0), - ] - returned: Annotated[ - int, Field(description='Number of creatives returned in this response', ge=0) - ] - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class Status173(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1117(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Billing(StrEnum): - operator = 'operator' - agent = 'agent' - advertiser = 'advertiser' - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role591(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role591, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class BillingEntity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PaymentTerms(StrEnum): - net_15 = 'net_15' - net_30 = 'net_30' - net_45 = 'net_45' - net_60 = 'net_60' - net_90 = 'net_90' - prepay = 'prepay' - - -class CreditLimit(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL where the human can complete the required action (credit application, legal agreement, add funds).' - ), - ] = None - message: Annotated[str, Field(description="Human-readable description of what's needed.")] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When this setup link expires.') - ] = None - - -class AccountScope(StrEnum): - operator = 'operator' - brand = 'brand' - operator_brand = 'operator_brand' - agent = 'agent' - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - - -class Protocol(StrEnum): - s3 = 's3' - gcs = 'gcs' - azure_blob = 'azure_blob' - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class Compression(StrEnum): - gzip = 'gzip' - none = 'none' - - -class ReportingBucket(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protocol: Annotated[ - Protocol, Field(description='Cloud storage protocol', title='Cloud Storage Protocol') - ] - bucket: Annotated[ - str, - Field( - description='Bucket or container name', - max_length=63, - min_length=3, - pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', - ), - ] - prefix: Annotated[ - str | None, - Field( - description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', - examples=['accounts/pinnacle/adcp', 'reporting/2024'], - max_length=512, - pattern='^[a-zA-Z0-9/_.-]+$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description='Cloud region for the bucket', - examples=['us-east-1', 'europe-west1'], - max_length=64, - pattern='^[a-z0-9-]+$', - ), - ] = None - format: Annotated[ - Format | None, - Field( - description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' - ), - ] = Format.jsonl - compression: Annotated[ - Compression | None, Field(description='Compression applied to delivered files') - ] = Compression.gzip - file_retention_days: Annotated[ - int, - Field( - description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', - examples=[14, 30, 90], - ge=1, - ), - ] - setup_instructions: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class Status174(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class DeclaredBy559(DeclaredBy): - pass - - -class VerifyAgent1118(VerifyAgent): - pass - - -class VerifyAgent1119(VerifyAgent1117): - pass - - -class VerificationItem559(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy560(DeclaredBy): - pass - - -class VerifyAgent1120(VerifyAgent): - pass - - -class VerifyAgent1121(VerifyAgent1117): - pass - - -class VerificationItem560(VerificationItem): - pass - - -class DeclaredBy561(DeclaredBy): - pass - - -class VerifyAgent1122(VerifyAgent): - pass - - -class VerifyAgent1123(VerifyAgent1117): - pass - - -class VerificationItem561(VerificationItem): - pass - - -class DeclaredBy562(DeclaredBy): - pass - - -class VerifyAgent1124(VerifyAgent): - pass - - -class VerifyAgent1125(VerifyAgent1117): - pass - - -class VerificationItem562(VerificationItem): - pass - - -class DeclaredBy563(DeclaredBy): - pass - - -class VerifyAgent1126(VerifyAgent): - pass - - -class VerifyAgent1127(VerifyAgent1117): - pass - - -class VerificationItem563(VerificationItem): - pass - - -class DeclaredBy564(DeclaredBy): - pass - - -class VerifyAgent1128(VerifyAgent): - pass - - -class VerifyAgent1129(VerifyAgent1117): - pass - - -class VerificationItem564(VerificationItem): - pass - - -class DeclaredBy565(DeclaredBy): - pass - - -class VerifyAgent1130(VerifyAgent): - pass - - -class VerifyAgent1131(VerifyAgent1117): - pass - - -class VerificationItem565(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy566(DeclaredBy): - pass - - -class VerifyAgent1132(VerifyAgent): - pass - - -class VerifyAgent1133(VerifyAgent1117): - pass - - -class VerificationItem566(VerificationItem): - pass - - -class DeclaredBy567(DeclaredBy): - pass - - -class VerifyAgent1134(VerifyAgent): - pass - - -class VerifyAgent1135(VerifyAgent1117): - pass - - -class VerificationItem567(VerificationItem): - pass - - -class DeclaredBy568(DeclaredBy): - pass - - -class VerifyAgent1136(VerifyAgent): - pass - - -class VerifyAgent1137(VerifyAgent1117): - pass - - -class VerificationItem568(VerificationItem): - pass - - -class DeclaredBy569(DeclaredBy): - pass - - -class VerifyAgent1138(VerifyAgent): - pass - - -class VerifyAgent1139(VerifyAgent1117): - pass - - -class VerificationItem569(VerificationItem): - pass - - -class DeclaredBy570(DeclaredBy): - pass - - -class VerifyAgent1140(VerifyAgent): - pass - - -class VerifyAgent1141(VerifyAgent1117): - pass - - -class VerificationItem570(VerificationItem): - pass - - -class DeclaredBy571(DeclaredBy): - pass - - -class VerifyAgent1142(VerifyAgent): - pass - - -class VerifyAgent1143(VerifyAgent1117): - pass - - -class VerificationItem571(VerificationItem): - pass - - -class DeclaredBy572(DeclaredBy): - pass - - -class VerifyAgent1144(VerifyAgent): - pass - - -class VerifyAgent1145(VerifyAgent1117): - pass - - -class VerificationItem572(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role606(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role606, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status175(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status175, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy573(DeclaredBy): - pass - - -class VerifyAgent1146(VerifyAgent): - pass - - -class VerifyAgent1147(VerifyAgent1117): - pass - - -class VerificationItem573(VerificationItem): - pass - - -class DeclaredBy574(DeclaredBy): - pass - - -class VerifyAgent1148(VerifyAgent): - pass - - -class VerifyAgent1149(VerifyAgent1117): - pass - - -class VerificationItem574(VerificationItem): - pass - - -class DeclaredBy575(DeclaredBy): - pass - - -class VerifyAgent1150(VerifyAgent): - pass - - -class VerifyAgent1151(VerifyAgent1117): - pass - - -class VerificationItem575(VerificationItem): - pass - - -class DeclaredBy576(DeclaredBy): - pass - - -class VerifyAgent1152(VerifyAgent): - pass - - -class VerifyAgent1153(VerifyAgent1117): - pass - - -class VerificationItem576(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy577(DeclaredBy): - pass - - -class VerifyAgent1154(VerifyAgent): - pass - - -class VerifyAgent1155(VerifyAgent1117): - pass - - -class VerificationItem577(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy578(DeclaredBy): - pass - - -class VerifyAgent1156(VerifyAgent): - pass - - -class VerifyAgent1157(VerifyAgent1117): - pass - - -class VerificationItem578(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy579(DeclaredBy): - pass - - -class VerifyAgent1158(VerifyAgent): - pass - - -class VerifyAgent1159(VerifyAgent1117): - pass - - -class VerificationItem579(VerificationItem): - pass - - -class Target56(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy580(DeclaredBy): - pass - - -class VerifyAgent1160(VerifyAgent): - pass - - -class VerifyAgent1161(VerifyAgent1117): - pass - - -class VerificationItem580(VerificationItem): - pass - - -class DeclaredBy581(DeclaredBy): - pass - - -class VerifyAgent1162(VerifyAgent): - pass - - -class VerifyAgent1163(VerifyAgent1117): - pass - - -class VerificationItem581(VerificationItem): - pass - - -class DeclaredBy582(DeclaredBy): - pass - - -class VerifyAgent1164(VerifyAgent): - pass - - -class VerifyAgent1165(VerifyAgent1117): - pass - - -class VerificationItem582(VerificationItem): - pass - - -class DeclaredBy583(DeclaredBy): - pass - - -class VerifyAgent1166(VerifyAgent): - pass - - -class VerifyAgent1167(VerifyAgent1117): - pass - - -class VerificationItem583(VerificationItem): - pass - - -class DeclaredBy584(DeclaredBy): - pass - - -class VerifyAgent1168(VerifyAgent): - pass - - -class VerifyAgent1169(VerifyAgent1117): - pass - - -class VerificationItem584(VerificationItem): - pass - - -class DeclaredBy585(DeclaredBy): - pass - - -class VerifyAgent1170(VerifyAgent): - pass - - -class VerifyAgent1171(VerifyAgent1117): - pass - - -class VerificationItem585(VerificationItem): - pass - - -class DeclaredBy586(DeclaredBy): - pass - - -class VerifyAgent1172(VerifyAgent): - pass - - -class VerifyAgent1173(VerifyAgent1117): - pass - - -class VerificationItem586(VerificationItem): - pass - - -class DeclaredBy587(DeclaredBy): - pass - - -class VerifyAgent1174(VerifyAgent): - pass - - -class VerifyAgent1175(VerifyAgent1117): - pass - - -class VerificationItem587(VerificationItem): - pass - - -class DeclaredBy588(DeclaredBy): - pass - - -class VerifyAgent1176(VerifyAgent): - pass - - -class VerifyAgent1177(VerifyAgent1117): - pass - - -class VerificationItem588(VerificationItem): - pass - - -class DeclaredBy589(DeclaredBy): - pass - - -class VerifyAgent1178(VerifyAgent): - pass - - -class VerifyAgent1179(VerifyAgent1117): - pass - - -class VerificationItem589(VerificationItem): - pass - - -class DeclaredBy590(DeclaredBy): - pass - - -class VerifyAgent1180(VerifyAgent): - pass - - -class VerifyAgent1181(VerifyAgent1117): - pass - - -class VerificationItem590(VerificationItem): - pass - - -class DeclaredBy591(DeclaredBy): - pass - - -class VerifyAgent1182(VerifyAgent): - pass - - -class VerifyAgent1183(VerifyAgent1117): - pass - - -class VerificationItem591(VerificationItem): - pass - - -class DeclaredBy592(DeclaredBy): - pass - - -class VerifyAgent1184(VerifyAgent): - pass - - -class VerifyAgent1185(VerifyAgent1117): - pass - - -class VerificationItem592(VerificationItem): - pass - - -class DeclaredBy593(DeclaredBy): - pass - - -class VerifyAgent1186(VerifyAgent): - pass - - -class VerifyAgent1187(VerifyAgent1117): - pass - - -class VerificationItem593(VerificationItem): - pass - - -class DeclaredBy594(DeclaredBy): - pass - - -class VerifyAgent1188(VerifyAgent): - pass - - -class VerifyAgent1189(VerifyAgent1117): - pass - - -class VerificationItem594(VerificationItem): - pass - - -class ReferenceAsset22(ReferenceAsset): - pass - - -class FeedFieldMapping23(FeedFieldMapping): - pass - - -class ReferenceAuthorization21(ReferenceAuthorization): - pass - - -class DeclaredBy595(DeclaredBy): - pass - - -class VerifyAgent1190(VerifyAgent): - pass - - -class VerifyAgent1191(VerifyAgent1117): - pass - - -class VerificationItem595(VerificationItem): - pass - - -class DeclaredBy596(DeclaredBy): - pass - - -class VerifyAgent1192(VerifyAgent): - pass - - -class VerifyAgent1193(VerifyAgent1117): - pass - - -class VerificationItem596(VerificationItem): - pass - - -class DeclaredBy597(DeclaredBy): - pass - - -class VerifyAgent1194(VerifyAgent): - pass - - -class VerifyAgent1195(VerifyAgent1117): - pass - - -class VerificationItem597(VerificationItem): - pass - - -class DeclaredBy598(DeclaredBy): - pass - - -class VerifyAgent1196(VerifyAgent): - pass - - -class VerifyAgent1197(VerifyAgent1117): - pass - - -class VerificationItem598(VerificationItem): - pass - - -class DeclaredBy599(DeclaredBy): - pass - - -class VerifyAgent1198(VerifyAgent): - pass - - -class VerifyAgent1199(VerifyAgent1117): - pass - - -class VerificationItem599(VerificationItem): - pass - - -class DeclaredBy600(DeclaredBy): - pass - - -class VerifyAgent1200(VerifyAgent): - pass - - -class VerifyAgent1201(VerifyAgent1117): - pass - - -class VerificationItem600(VerificationItem): - pass - - -class DeclaredBy601(DeclaredBy): - pass - - -class VerifyAgent1202(VerifyAgent): - pass - - -class VerifyAgent1203(VerifyAgent1117): - pass - - -class VerificationItem601(VerificationItem): - pass - - -class DeclaredBy602(DeclaredBy): - pass - - -class VerifyAgent1204(VerifyAgent): - pass - - -class VerifyAgent1205(VerifyAgent1117): - pass - - -class VerificationItem602(VerificationItem): - pass - - -class VariableType(StrEnum): - text = 'text' - image = 'image' - video = 'video' - audio = 'audio' - url = 'url' - number = 'number' - boolean = 'boolean' - color = 'color' - date = 'date' - - -class Variable(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - variable_id: Annotated[str, Field(description='Variable identifier on the creative platform')] - name: Annotated[str, Field(description='Human-readable variable name')] - variable_type: Annotated[ - VariableType, - Field( - description='Data type of the variable. Each type represents a semantic content slot: text (headlines, body copy), image/video/audio (media URLs), url (clickthrough or tracking URLs), number (prices, counts), boolean (conditional flags like show_discount or is_raining), color (hex color values), date (ISO 8601 date-time for countdowns and offer expirations).' - ), - ] - default_value: Annotated[ - str | None, - Field( - description='Default value used when no dynamic value is provided at serve time. All types are string-encoded: text/image/video/audio/url as literal strings, number as decimal (e.g., "42.99"), boolean as "true"/"false", color as "#RRGGBB", date as ISO 8601 (e.g., "2026-12-25T00:00:00Z").' - ), - ] = None - required: Annotated[ - bool | None, - Field(description='Whether this variable must have a value for the creative to serve'), - ] = False - - -class AssignedPackage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description='Package identifier')] - assigned_date: Annotated[AwareDatetime, Field(description='When this assignment was created')] - - -class Assignments(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - assignment_count: Annotated[ - int, Field(description='Total number of active package assignments', ge=0) - ] - assigned_packages: Annotated[ - list[AssignedPackage] | None, - Field(description='List of packages this creative is assigned to'), - ] = None - - -class Snapshot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - as_of: Annotated[ - AwareDatetime, Field(description='When this snapshot was captured by the platform') - ] - staleness_seconds: Annotated[ - int, - Field( - description='Maximum age of this data in seconds. For example, 3600 means the data may be up to 1 hour old.', - ge=0, - ), - ] - impressions: Annotated[ - int, - Field( - description='Lifetime impressions across all assignments. Not scoped to any date range.', - ge=0, - ), - ] - last_served: Annotated[ - AwareDatetime | None, - Field( - description='Last time this creative served an impression. Absent when the creative has never served.' - ), - ] = None - - -class SnapshotUnavailableReason(StrEnum): - SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED' - SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE' - SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED' - - -class Items1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_kind: Annotated[ - Literal['media'], - Field(description='Discriminator indicating this is a media asset with content_uri'), - ] = 'media' - asset_type: Annotated[ - str, - Field( - description='Type of asset. Common types: thumbnail_image, product_image, featured_image, logo' - ), - ] - asset_id: Annotated[ - str, Field(description='Unique identifier for the asset within the creative') - ] - content_uri: Annotated[AnyUrl, Field(description='URL for media assets (images, videos, etc.)')] - - -class Items2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_kind: Annotated[ - Literal['text'], - Field(description='Discriminator indicating this is a text asset with content'), - ] = 'text' - asset_type: Annotated[ - str, - Field( - description='Type of asset. Common types: headline, body_text, cta_text, price_text, sponsor_name, author_name, click_url' - ), - ] - asset_id: Annotated[ - str, Field(description='Unique identifier for the asset within the creative') - ] - content: Annotated[ - str | list[str], - Field( - description='Text content for text-based assets like headlines, body text, CTA text, etc.' - ), - ] - - -class Items(RootModel[Items1 | Items2]): - root: Annotated[ - Items1 | Items2, - Field( - description='Item within a multi-asset creative format. Used for carousel products, native ad components, and other formats composed of multiple distinct elements.', - discriminator='asset_kind', - title='Creative Item', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption206(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption207(PricingOption201, PricingOption206): - pass - - -class PricingOption208(PricingOption202, PricingOption206): - pass - - -class PricingOption209(PricingOption203, PricingOption206): - pass - - -class PricingOption2010(PricingOption204, PricingOption206): - pass - - -class PricingOption2011(PricingOption205, PricingOption206): - pass - - -class PricingOption( - RootModel[ - PricingOption207 - | PricingOption208 - | PricingOption209 - | PricingOption2010 - | PricingOption2011 - ] -): - root: Annotated[ - PricingOption207 - | PricingOption208 - | PricingOption209 - | PricingOption2010 - | PricingOption2011, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ReasonCode(StrEnum): - review_passed = 'review_passed' - review_failure = 'review_failure' - processing_failure = 'processing_failure' - seller_rereview = 'seller_rereview' - policy_revocation = 'policy_revocation' - content_drift = 'content_drift' - identity_authorization_revoked = 'identity_authorization_revoked' - identity_authorization_expired = 'identity_authorization_expired' - source_private = 'source_private' - source_deleted = 'source_deleted' - takedown_request = 'takedown_request' - advertiser_request = 'advertiser_request' - seller_archive = 'seller_archive' - account_closed = 'account_closed' - account_suspended = 'account_suspended' - retention_expired = 'retention_expired' - legal_erasure = 'legal_erasure' - - -class Purge(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['soft'], - Field(description='Always `soft` on tombstones — hard purges do not surface on this read.'), - ] = 'soft' - at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the creative was destroyed. Matches the `purged_at` field on the corresponding `creative.purged` webhook fire.' - ), - ] - reason_code: Annotated[ - ReasonCode, - Field( - description='Categorical reason for the purge. Matches the value the seller emitted on the corresponding `creative.purged` webhook fire.', - title='Creative Event Reason Code', - ), - ] - - -class Status177(StrEnum): - success = 'success' - failed = 'failed' - timeout = 'timeout' - connection_error = 'connection_error' - pending = 'pending' - - -class StatusSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - processing: Annotated[ - int | None, Field(description='Number of creatives being processed', ge=0) - ] = None - approved: Annotated[int | None, Field(description='Number of approved creatives', ge=0)] = None - pending_review: Annotated[ - int | None, Field(description='Number of creatives pending review', ge=0) - ] = None - rejected: Annotated[int | None, Field(description='Number of rejected creatives', ge=0)] = None - archived: Annotated[int | None, Field(description='Number of archived creatives', ge=0)] = None - - -class Issue73(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue73] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - impairment = 'impairment' - creative_status_changed = 'creative.status_changed' - creative_purged = 'creative.purged' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction581] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Authentication40(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class NotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication40 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: Annotated[ - Status173, - Field( - description='Account lifecycle status. See the Accounts Protocol overview for the operations matrix showing which tasks are permitted in each state.', - title='Account Status', - ), - ] - brand: Annotated[ - Brand | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: Annotated[ - Billing | None, - Field( - description="Who is invoiced on this account. See billing_entity for the invoiced party's business details.", - title='Billing Party', - ), - ] = None - billing_entity: Annotated[ - BillingEntity | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: Annotated[ - PaymentTerms | None, - Field( - description='Payment terms agreed for this account. Binding for all invoices when the account is active.', - title='Payment Terms', - ), - ] = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: Annotated[ - AccountScope | None, - Field( - description='How the seller scoped a billing account relative to the operator and brand dimensions.', - title='Account Scope', - ), - ] = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem559(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark559(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction582(Jurisdiction581): - pass - - -class Disclosure561(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction582] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance559(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy559 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem559] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark559] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure561 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem559] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance559 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem560(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark560(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction583(Jurisdiction581): - pass - - -class Disclosure562(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction583] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance560(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy560 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem560] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark560] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure562 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem560] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance560 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem561(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark561(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction584(Jurisdiction581): - pass - - -class Disclosure563(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction584] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance561(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy561 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem561] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark561] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure563 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem561] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance561 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem562(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark562(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction585(Jurisdiction581): - pass - - -class Disclosure564(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction585] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance562(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy562 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem562] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark562] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure564 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem562] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance562 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem563(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark563(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction586(Jurisdiction581): - pass - - -class Disclosure565(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction586] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance563(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy563 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem563] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark563] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure565 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem563] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance563 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem564(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark564(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction587(Jurisdiction581): - pass - - -class Disclosure566(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction587] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance564(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy564 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem564] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark564] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure566 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem564] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance564 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem565(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark565(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction588(Jurisdiction581): - pass - - -class Disclosure567(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction588] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance565(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy565 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem565] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark565] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure567 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem565] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance565 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem566(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark566(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction589(Jurisdiction581): - pass - - -class Disclosure568(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction589] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance566(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy566 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem566] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark566] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure568 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem566] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance566 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem567(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark567(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction590(Jurisdiction581): - pass - - -class Disclosure569(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction590] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance567(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy567 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem567] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark567] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure569 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem567] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance567 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem568(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark568(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction591(Jurisdiction581): - pass - - -class Disclosure570(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction591] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance568(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy568 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem568] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark568] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure570 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem568] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance568 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem569(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark569(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction592(Jurisdiction581): - pass - - -class Disclosure571(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction592] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance569(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy569 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem569] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark569] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure571 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem569] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance569 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem570(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark570(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction593(Jurisdiction581): - pass - - -class Disclosure572(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction593] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance570(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy570 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem570] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark570] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure572 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem570] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance570 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem571(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark571(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction594(Jurisdiction581): - pass - - -class Disclosure573(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction594] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance571(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy571 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem571] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark571] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure573 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem571] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance571 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem572(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark572(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction595(Jurisdiction581): - pass - - -class Disclosure574(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction595] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance572(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy572 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem572] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark572] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure574 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem572] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance572 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets330(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets331(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem573(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark573(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction597(Jurisdiction581): - pass - - -class Disclosure575(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction597] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance573(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy573 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem573] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark573] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure575 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem573] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance573 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem574(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark574(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction598(Jurisdiction581): - pass - - -class Disclosure576(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction598] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance574(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy574 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem574] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark574] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure576 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem574] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance574 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem575(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark575(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction599(Jurisdiction581): - pass - - -class Disclosure577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction599] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance575(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy575 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem575] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark575] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure577 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem575] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance575 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem576(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark576(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction600(Jurisdiction581): - pass - - -class Disclosure578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction600] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance576(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy576 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem576] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark576] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure578 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem576] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance576 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction601(Jurisdiction581): - pass - - -class Disclosure579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction601] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy577 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem577] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark577] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure579 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem577] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media41, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance577 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction602(Jurisdiction581): - pass - - -class Disclosure580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction602] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy578 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem578] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark578] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure580 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem578] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance578 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction603(Jurisdiction581): - pass - - -class Disclosure581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction603] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy579 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem579] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark579] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure581 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem579] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance579 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction604(Jurisdiction581): - pass - - -class Disclosure582(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction604] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy580 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem580] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark580] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure582 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem580] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target56 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target56.linear - provenance: Annotated[ - Provenance580 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction605(Jurisdiction581): - pass - - -class Disclosure583(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction605] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy581 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem581] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark581] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure583 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem581] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance581 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem582(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark582(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction606(Jurisdiction581): - pass - - -class Disclosure584(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction606] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance582(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy582 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem582] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark582] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure584 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem582] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance582 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem583(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark583(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction607(Jurisdiction581): - pass - - -class Disclosure585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction607] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance583(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy583 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem583] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark583] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure585 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem583] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance583 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem584(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark584(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction608(Jurisdiction581): - pass - - -class Disclosure586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction608] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance584(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy584 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem584] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark584] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure586 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem584] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance584 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction609(Jurisdiction581): - pass - - -class Disclosure587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction609] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy585 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem585] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark585] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure587 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem585] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance585 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction610(Jurisdiction581): - pass - - -class Disclosure588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction610] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy586 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem586] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark586] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure588 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem586] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance586 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction611(Jurisdiction581): - pass - - -class Disclosure589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction611] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy587 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem587] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark587] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure589 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem587] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance587 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction612(Jurisdiction581): - pass - - -class Disclosure590(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction612] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy588 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem588] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark588] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure590 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem588] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance588 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction613(Jurisdiction581): - pass - - -class Disclosure591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction613] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy589 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem589] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark589] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure591 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem589] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance589 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem590(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark590(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction614(Jurisdiction581): - pass - - -class Disclosure592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction614] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance590(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy590 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem590] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark590] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure592 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem590] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance590 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction615(Jurisdiction581): - pass - - -class Disclosure593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction615] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy591 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem591] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark591] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure593 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem591] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance591 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction616(Jurisdiction581): - pass - - -class Disclosure594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction616] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy592 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem592] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark592] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure594 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem592] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance592 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction617(Jurisdiction581): - pass - - -class Disclosure595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction617] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy593 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem593] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark593] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure595 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem593] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance593 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction618(Jurisdiction581): - pass - - -class Disclosure596(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction618] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy594 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem594] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark594] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure596 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem594] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance594 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure22(RequiredDisclosure): - pass - - -class Compliance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure22] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets33717(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset22] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance22 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets33718(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping23] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction620(Jurisdiction581): - pass - - -class Disclosure597(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction620] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy595 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem595] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark595] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure597 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem595] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization21 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance595 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem596(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1192 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark596(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1193 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction621(Jurisdiction581): - pass - - -class Disclosure598(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction621] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance596(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy596 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem596] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark596] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure598 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem596] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance596 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem597(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1194 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark597(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1195 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction622(Jurisdiction581): - pass - - -class Disclosure599(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction622] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance597(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy597 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem597] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark597] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure599 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem597] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance597 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem598(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1196 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark598(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1197 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction623(Jurisdiction581): - pass - - -class Disclosure600(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction623] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance598(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy598 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem598] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark598] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure600 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem598] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance598 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem599(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1198 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark599(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1199 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction624(Jurisdiction581): - pass - - -class Disclosure601(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction624] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance599(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy599 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem599] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark599] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure601 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem599] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media42 | Media43, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl21 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance599 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem600(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1200 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark600(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1201 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction625(Jurisdiction581): - pass - - -class Disclosure602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction625] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance600(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy600 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem600] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark600] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure602 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem600] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance600 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem601(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1202 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark601(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1203 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction626(Jurisdiction581): - pass - - -class Disclosure603(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction626] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance601(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy601 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem601] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark601] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure603 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem601] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance601 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1204 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1205 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction627(Jurisdiction581): - pass - - -class Disclosure604(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction627] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy602 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem602] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark602] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure604 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem602] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target56 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target56.linear - provenance: Annotated[ - Provenance602 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets3371( - RootModel[ - Assets3372 - | Assets3373 - | Assets3374 - | Assets3375 - | Assets3376 - | Assets3377 - | Assets3378 - | Assets3379 - | Assets33710 - | Assets33711 - | Assets33712 - | Assets33713 - | Assets33714 - | Assets33715 - | Assets329 - | Assets33717 - | Assets33718 - | Assets33719 - | Assets33720 - | Assets33721 - | Assets33722 - | Assets33723 - ] -): - root: Annotated[ - Assets3372 - | Assets3373 - | Assets3374 - | Assets3375 - | Assets3376 - | Assets3377 - | Assets3378 - | Assets3379 - | Assets33710 - | Assets33711 - | Assets33712 - | Assets33713 - | Assets33714 - | Assets33715 - | Assets329 - | Assets33717 - | Assets33718 - | Assets33719 - | Assets33720 - | Assets33721 - | Assets33722 - | Assets33723, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets337(RootModel[list[Assets3371]]): - root: Annotated[list[Assets3371], Field(min_length=1)] - - -class WebhookActivityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Equals the `idempotency_key` carried in the webhook payload itself (see docs/building/by-layer/L3/webhooks.mdx § Dedup by `idempotency_key`). Stable across retry attempts of the same logical fire — retries with `attempt` > 1 reuse this key. Buyers correlate this surface with their own endpoint logs via this exact field; the spec deliberately reuses the payload key rather than minting a parallel `delivery_id` so callers do not need a join table. Format is sender-defined; callers MUST treat as opaque.' - ), - ] - subscriber_id: Annotated[ - str | None, - Field( - description='Identifies which registered webhook subscriber received this fire. **Required on records from account-anchored notification channels** (`notification_configs[]` registered via `sync_accounts`) — every subscriber has a `subscriber_id` at registration time, the seller MUST echo it on every fire and every activity record. **Optional on records from per-resource push channels** (`push_notification_config` on a media buy or task) — the calling principal is unambiguous in single-subscriber configurations and the field MAY be omitted; sellers MUST populate it once `reporting_webhook` adopts multi-subscriber (per #3009 in AdCP 4.0). Buyers MUST NOT use absence as a signal that no other subscribers exist; that information is not exposed by this surface.' - ), - ] = None - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated the HTTP request for this attempt.' - ), - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp when the seller observed the response (or terminal timeout / connection error — for `timeout` and `connection_error` outcomes, `completed_at` is set to the moment the seller declared the attempt terminal). Explicitly `null` when the attempt is still in flight or queued for retry (status `pending`); MUST be set as `null` rather than omitted so callers can distinguish 'still in flight' from 'field missing'." - ), - ] = None - notification_type: NotificationType - sequence_number: Annotated[ - int | None, - Field( - description='Sequence number from the webhook payload. Surfaced here so the buyer can spot stale-sequence drops and gaps without correlating against their own endpoint log. Absent for notification types that do not carry a sequence number.', - ge=0, - ), - ] = None - attempt: Annotated[ - int, - Field( - description='1-indexed retry counter for this logical fire. Initial fire is attempt=1; retries increment. Sellers MUST emit one record per attempt, so a successful first-attempt fire appears as a single record with `attempt: 1` and a 3-attempt retry trail appears as three records sharing `idempotency_key`.', - ge=1, - ), - ] - status: Annotated[ - Status177, - Field( - description="Outcome of this attempt. `success` — response received with 2xx (`http_status_code` populated). `failed` — response received with non-2xx (`http_status_code` populated). `timeout` — no response within the seller's configured timeout (`http_status_code` null). `connection_error` — DNS / TLS / socket failure before any HTTP response (`http_status_code` null). `pending` — attempt is in flight or queued for retry (`completed_at` null, `http_status_code` null). The `timeout` / `connection_error` split is intentional and operationally distinct: `timeout` typically signals a slow / overloaded buyer endpoint, `connection_error` typically signals it is unreachable or misconfigured." - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Target URL for this fire. Query string and fragment MUST be stripped before surfacing — buyers commonly stash bearer tokens in the query string and sellers MUST NOT echo those back through this debug surface. Sellers SHOULD additionally redact path segments matching obvious secret patterns (e.g., a path segment that is high-entropy random material or matches a UUID / token format). Buyers matching this against their own configured URL should compare by origin + path; query strings will not match and that mismatch is expected.' - ), - ] - http_status_code: Annotated[ - int | None, - Field( - description="HTTP status code returned by the buyer's endpoint. Explicitly `null` when no HTTP response was received (status `timeout`, `connection_error`, or `pending`); MUST be set as `null` rather than omitted.", - ge=100, - le=599, - ), - ] = None - response_time_ms: Annotated[ - int | None, - Field( - description='Wall-clock latency between request send and response receipt, in milliseconds. Explicitly `null` when the attempt did not complete (`timeout`, `connection_error`, `pending`); MUST be set as `null` rather than omitted.', - ge=0, - ), - ] = None - payload_size_bytes: Annotated[ - int | None, - Field( - description="Size of the request body the seller sent, in bytes. Useful for diagnosing oversized-payload rejections from the buyer's gateway.", - ge=0, - ), - ] = None - error_message: Annotated[ - str | None, - Field( - description='Short human-readable server-side classification of why this attempt did not succeed (e.g., `connection refused`, `TLS handshake timeout`, `HTTP 503 Service Unavailable`). Explicitly `null` for `success` (MUST be set as `null` rather than omitted). Sellers MUST NOT include request headers, request body content, or response body content in this field — payload surfacing is reserved for a future `include_webhook_payloads` extension and is subject to stricter access controls. Sellers SHOULD also avoid including buyer-endpoint internal hostnames, stack traces, or other implementation detail leaked by the response — keep it a stable classification string.', - max_length=500, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Resource-specific extension slot. Adopters MAY surface a resource-specific cross-reference (e.g., `creative_id` on a creative-lifecycle record, `media_buy_id` on a record nested inside an account-level read) under `ext` rather than adding top-level fields — the canonical record shape stays uniform across resources and the `ext` envelope absorbs per-resource needs. Top-level extensions are not permitted (`additionalProperties: false`).', - title='Extension Object', - ), - ] = None - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - account: Annotated[ - Account | None, - Field( - description='Account that owns this creative', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Format identifier specifying which format this creative conforms to', - title='Format Reference (Structured Object)', - ), - ] - status: Annotated[ - Status174, - Field(description='Current approval status of the creative', title='Creative Status'), - ] - created_date: Annotated[AwareDatetime, Field(description='When the creative was created')] - updated_date: Annotated[AwareDatetime, Field(description='When the creative was last modified')] - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets316 - | Assets317 - | Assets318 - | Assets319 - | Assets320 - | Assets321 - | Assets322 - | Assets323 - | Assets324 - | Assets325 - | Assets326 - | Assets327 - | Assets328 - | Assets329 - | Assets330 - | Assets331 - | Assets332 - | Assets333 - | Assets334 - | Assets335 - | Assets336 - | Assets337, - ] - | None, - Field( - description='Assets for this creative, keyed by asset_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema.' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - concept_id: Annotated[ - str | None, - Field( - description='Creative concept this creative belongs to. Concepts group related creatives across sizes and formats.' - ), - ] = None - concept_name: Annotated[str | None, Field(description='Human-readable concept name')] = None - variables: Annotated[ - list[Variable] | None, - Field( - description='Dynamic content variables (DCO slots) for this creative. Included when include_variables=true.' - ), - ] = None - assignments: Annotated[ - Assignments | None, - Field(description='Current package assignments (included when include_assignments=true)'), - ] = None - snapshot: Annotated[ - Snapshot | None, - Field( - description='Lightweight delivery snapshot (included when include_snapshot=true). For detailed performance analytics, use get_creative_delivery.' - ), - ] = None - snapshot_unavailable_reason: Annotated[ - SnapshotUnavailableReason | None, - Field( - description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot data is unavailable for this creative.', - title='Snapshot Unavailable Reason', - ), - ] = None - items: Annotated[ - list[Items] | None, - Field( - description='Items for multi-asset formats like carousels and native ads (included when include_items=true)' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options for using this creative (serving, delivery). Used by ad servers and library agents. Transformation agents expose format-level pricing on list_creative_formats instead. Present when include_pricing=true and account provided. The buyer passes the applied pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - purge: Annotated[ - Purge | None, - Field( - description="Tombstone block — present only when this record is a soft-purged creative surfaced via `include_purged: true`. The record's `status` field reflects the last status before purge (frozen — buyers MUST treat the creative as gone; assignments, snapshot, and serving operations no longer apply). Tombstones surface for the seller's webhook activity retention window (30 days from `purge.at`). Hard purges (`purge_kind: hard` on the webhook) do not surface on this read — the [`creative.purged`](https://adcontextprotocol.org/schemas/v3/creative/creative-purged-webhook.json) webhook is the only signal." - ), - ] = None - webhook_activity: Annotated[ - list[WebhookActivityItem] | None, - Field( - description='Recent webhook fires scoped to this creative — `creative.status_changed` and `creative.purged` deliveries. Present only when the request set `include_webhook_activity: true`. Each item is a `webhook-activity-record`; the `notification_type` field discriminates between status changes and purges. The `ext.creative_id` slot MAY be populated on records nested inside larger reads where the parent does not already key the array; on `list_creatives` the parent creative_id is unambiguous and `ext.creative_id` MAY be omitted. Retention: 30 days from `completed_at` (MUST). See `snapshot-and-log.mdx § Webhook activity log pattern` for the full normative contract.', - max_length=200, - ), - ] = None - - -class ListCreativesResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - pagination: Annotated[ - Pagination, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] - creatives: Annotated[ - Sequence[Creative], Field(description='Array of creative assets matching the query') - ] - format_summary: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], int] | None, - Field( - description="Breakdown of creatives by format. Keys are agent-defined format identifiers, optionally including dimensions (e.g., 'display_static_300x250', 'video_30s_vast'). Key construction is platform-specific — there is no required format." - ), - ] = None - status_summary: Annotated[ - StatusSummary | None, Field(description='Breakdown of creatives by status') - ] = None - errors: Annotated[ - list[Error] | None, - Field(description='Task-specific errors (e.g., invalid filters, account not found)'), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_transformers_request.py b/src/adcp/types/generated_poc/bundled/creative/list_transformers_request.py deleted file mode 100644 index 752b5292..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_transformers_request.py +++ /dev/null @@ -1,740 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_transformers_request.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class InputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class OutputFormatId(InputFormatId): - pass - - -class ExpandPaginationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transformer_id: Annotated[ - str, Field(description='The transformer whose param options to page.') - ] - field: Annotated[str, Field(description='The param `field` to page.')] - options_cursor: Annotated[ - str, Field(description="Opaque cursor from that param's prior `params[].options_cursor`.") - ] - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1215(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1215 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account40(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class ListTransformersRequestCreativeAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - transformer_ids: Annotated[ - list[str] | None, - Field(description='Return only these specific transformer IDs.', min_length=1), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - description='Filter to transformers that accept any of these formats as input.', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId] | None, - Field( - description='Filter to transformers that can produce any of these output formats.', - min_length=1, - ), - ] = None - name_search: Annotated[ - str | None, - Field(description='Search transformers by name (case-insensitive partial match).'), - ] = None - brief: Annotated[ - str | None, - Field( - description="Natural-language brief used to rank and filter transformers (and their enumerable option values when expanded) — e.g. 'warm female Spanish-language voiceover'. Curates to intent rather than returning the full set, the way get_products curates inventory." - ), - ] = None - expand_params: Annotated[ - list[str] | None, - Field( - description="Param `field` names for which to return the FIRST page of account-scoped option VALUES inline on each transformer's `params[].options[]`. Omit to return param descriptors without enumerated values (the lean default). When a param's options are truncated, its `params[].options_cursor` is set — fetch the next page via `expand_pagination` (below).", - min_length=1, - ), - ] = None - expand_pagination: Annotated[ - list[ExpandPaginationItem] | None, - Field( - description="Fetch the NEXT page of a specific param's account-scoped options, using the `options_cursor` a prior response returned for that `(transformer, param)`. Scoped per `(transformer_id, field)` so multiple params can be paged independently. Use this instead of `expand_params` once you hold a cursor.", - min_length=1, - ), - ] = None - include_pricing: Annotated[ - bool | None, - Field(description='Include `pricing_options` on each transformer. Requires `account`.'), - ] = False - account: Annotated[ - Account | Account40 | None, - Field( - description='Account reference. Transformers are account-scoped — the returned set, the enumerable option values, and (with include_pricing) the rate card are all resolved for this credential.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/list_transformers_response.py b/src/adcp/types/generated_poc/bundled/creative/list_transformers_response.py deleted file mode 100644 index 9d3ec280..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/list_transformers_response.py +++ /dev/null @@ -1,883 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/list_transformers_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class BrandAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the brand agent.')] - id: Annotated[str, Field(description='Agent identifier.')] - - -class VoiceSynthesisRefItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_agent: Annotated[ - BrandAgent, - Field(description='Brand agent that exposed the referenced voice_synthesis entry.'), - ] - voice_id: Annotated[str, Field(description='voice_synthesis.voice_id from the brand agent.')] - rights_id: Annotated[ - str | None, - Field( - description='Optional rights offering or grant identifier associated with this provisioned voice. This is provenance metadata only, not a build_creative rights token.' - ), - ] = None - - -class InputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class OutputFormatId(InputFormatId): - pass - - -class Type(StrEnum): - string = 'string' - number = 'number' - integer = 'integer' - boolean = 'boolean' - - -class ValueSource(StrEnum): - inline = 'inline' - range = 'range' - enumerable = 'enumerable' - free_text = 'free_text' - - -class Option(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - value: Annotated[ - Any, Field(description='The value the buyer passes in `config` for this field.') - ] - label: Annotated[str | None, Field(description='Human-readable label for this option.')] = None - metadata: Annotated[ - dict[str, Any] | None, - Field( - description='Option-specific attributes the buyer can filter or display (e.g. for a voice: language, gender, provider, custom).' - ), - ] = None - - -class Param(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[ - str, - Field( - description='The config key. Buyers set the value under this exact key in build_creative `config`.' - ), - ] - type: Annotated[ - Type, Field(description='JSON type of the value the buyer supplies for this field.') - ] - value_source: Annotated[ - ValueSource, - Field( - description='Where the legal values come from. `inline` — a small closed set listed in `allowed_values` (e.g. mastering_preset). `range` — a numeric interval bounded by `minimum`/`maximum` (e.g. speaking_rate). `enumerable` — an account-scoped, dynamic set (e.g. voices, including custom/cloned ones) resolved per-credential; values appear in `options[]` only when expanded. `free_text` — an open buyer-authored string with no closed/enumerable set (e.g. a negative_prompt or style note for a generative agent); `type` MUST be `string` and `allowed_values`/`minimum`/`maximum`/`options`/`options_cursor` MUST be absent. NOTE: a transformer-param MUST NOT be a generation-count knob (sample_count/n/num_images/count) — output count is owned by `max_variants`/`max_creatives`, never config.' - ), - ] - max_length: Annotated[ - int | None, - Field( - description='Optional maximum character length for a `free_text` param. Omit for no declared limit.', - ge=1, - ), - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='The closed set of legal values. Present when `value_source` is `inline`.', - min_length=1, - ), - ] = None - minimum: Annotated[ - float | None, - Field(description='Inclusive lower bound. Present when `value_source` is `range`.'), - ] = None - maximum: Annotated[ - float | None, - Field(description='Inclusive upper bound. Present when `value_source` is `range`.'), - ] = None - options: Annotated[ - list[Option] | None, - Field( - description="Account-scoped legal values for an `enumerable` param. Populated ONLY when this param's `field` was named in the list_transformers `expand_params` request — otherwise omitted (the buyer enumerates on demand). Brief-filtered and paginated; use `options_cursor` for the next page." - ), - ] = None - options_cursor: Annotated[ - str | None, - Field( - description="Opaque pagination cursor for this param's `options[]`. Present when more option values are available than were returned. Pass back to list_transformers (scoped to this transformer + field) to fetch the next page." - ), - ] = None - default: Annotated[ - Any | None, - Field( - description='The value applied when the buyer omits this field from `config`. Type matches `type`.' - ), - ] = None - required: Annotated[ - bool | None, - Field( - description='Whether the buyer MUST supply this field in `config`. When false and no `default` is declared, the agent chooses.' - ), - ] = False - description: Annotated[ - str | None, Field(description='Human-readable explanation of what this knob does.') - ] = None - - -class AppliesToOutputFormatId(InputFormatId): - pass - - -class PricingOption221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption226(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption227(PricingOption221, PricingOption226): - pass - - -class PricingOption228(PricingOption222, PricingOption226): - pass - - -class PricingOption229(PricingOption223, PricingOption226): - pass - - -class PricingOption2210(PricingOption224, PricingOption226): - pass - - -class PricingOption2211(PricingOption225, PricingOption226): - pass - - -class PricingOption( - RootModel[ - PricingOption227 - | PricingOption228 - | PricingOption229 - | PricingOption2210 - | PricingOption2211 - ] -): - root: Annotated[ - PricingOption227 - | PricingOption228 - | PricingOption229 - | PricingOption2210 - | PricingOption2211, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class VariantDimension(StrEnum): - voice = 'voice' - theme = 'theme' - best_of_n = 'best_of_n' - transformer_config = 'transformer_config' - custom = 'custom' - - -class Multiplicity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supports_catalog_fanout: Annotated[ - bool | None, Field(description='Whether this transformer accepts max_creatives.') - ] = False - max_creatives_limit: Annotated[ - int | None, - Field(description='Per-transformer ceiling on max_creatives (≤ the agent ceiling).', ge=1), - ] = None - supports_variants: Annotated[ - bool | None, - Field(description='Whether this transformer accepts max_variants > 1 / variant_axis.'), - ] = False - max_variants_limit: Annotated[ - int | None, - Field(description='Per-transformer ceiling on max_variants (≤ the agent ceiling).', ge=1), - ] = None - variant_dimensions: Annotated[ - list[VariantDimension] | None, - Field(description="Variant axis dimensions this transformer supports (⊆ the agent's)."), - ] = None - - -class Transformer(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - transformer_id: Annotated[ - str, - Field( - description='Stable identifier for this transformer within the agent. Pass to build_creative `transformer_id` to select it.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable transformer name (e.g. 'Voiceover — Isaac', 'Veo 3 text-to-video')." - ), - ] - description: Annotated[ - str | None, - Field(description='Plain-text explanation of what this transformer produces and how.'), - ] = None - metadata: Annotated[ - dict[str, Any] | None, - Field( - description='Transformer-specific attributes a buyer can filter or display (e.g. provider, modality, language).' - ), - ] = None - voice_synthesis_ref: Annotated[ - list[VoiceSynthesisRefItem] | None, - Field( - description='Optional discovery/audit anchors for voice transformers provisioned from brand-agent voice_synthesis entries. Informational only: these references help buyers match a discovered transformer to brand/rights-agent provenance, but they do not assert build-time authorization or require the creative agent to perform rights-token validation.', - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - description='Formats this transformer accepts as input. Empty or omitted means it builds from a brief / raw assets (pure generation) rather than transforming an existing creative.' - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId], - Field( - description="Formats this transformer can produce. A build_creative request's target format(s) MUST be a subset of these.", - min_length=1, - ), - ] - params: Annotated[ - list[Param] | None, - Field( - description="Configuration knobs this transformer exposes. The buyer supplies values in build_creative `config`, keyed by each param's `field`. Enumerable param values (e.g. account-specific voices) are returned only when requested via list_transformers `expand_params`." - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Per-account rate-card options for using this transformer. Present when the list_transformers request set include_pricing=true with an account. The applied option is echoed back per-leaf on the build_creative response and reconciled via report_usage.', - min_length=1, - ), - ] = None - multiplicity: Annotated[ - Multiplicity | None, - Field( - description="Optional per-transformer fan-out limits that NARROW the agent-level get_adcp_capabilities `creative.multiplicity`. Same shape as the agent-level object. When present, this transformer's authoritative; its ceilings (max_creatives_limit / max_variants_limit) MUST NOT exceed the agent ceilings, and its variant_dimensions MUST be a subset of the agent's. Omit to inherit the agent-level capability unchanged." - ), - ] = None - - -class Issue77(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue77] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class ListTransformersResponseCreativeAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - transformers: Annotated[ - list[Transformer], Field(description='Transformer descriptors matching the query.') - ] - errors: Annotated[ - list[Error] | None, Field(description='Task-specific errors and warnings.') - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/preview_creative_request.py b/src/adcp/types/generated_poc/bundled/creative/preview_creative_request.py deleted file mode 100644 index 09fccb56..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/preview_creative_request.py +++ /dev/null @@ -1,40895 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/preview_creative_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class RequestType(StrEnum): - single = 'single' - batch = 'batch' - variant = 'variant' - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1217(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy609(DeclaredBy): - pass - - -class VerifyAgent1218(VerifyAgent): - pass - - -class VerifyAgent1219(VerifyAgent1217): - pass - - -class VerificationItem609(VerificationItem): - pass - - -class DeclaredBy610(DeclaredBy): - pass - - -class VerifyAgent1220(VerifyAgent): - pass - - -class VerifyAgent1221(VerifyAgent1217): - pass - - -class VerificationItem610(VerificationItem): - pass - - -class DeclaredBy611(DeclaredBy): - pass - - -class VerifyAgent1222(VerifyAgent): - pass - - -class VerifyAgent1223(VerifyAgent1217): - pass - - -class VerificationItem611(VerificationItem): - pass - - -class DeclaredBy612(DeclaredBy): - pass - - -class VerifyAgent1224(VerifyAgent): - pass - - -class VerifyAgent1225(VerifyAgent1217): - pass - - -class VerificationItem612(VerificationItem): - pass - - -class DeclaredBy613(DeclaredBy): - pass - - -class VerifyAgent1226(VerifyAgent): - pass - - -class VerifyAgent1227(VerifyAgent1217): - pass - - -class VerificationItem613(VerificationItem): - pass - - -class DeclaredBy614(DeclaredBy): - pass - - -class VerifyAgent1228(VerifyAgent): - pass - - -class VerifyAgent1229(VerifyAgent1217): - pass - - -class VerificationItem614(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy615(DeclaredBy): - pass - - -class VerifyAgent1230(VerifyAgent): - pass - - -class VerifyAgent1231(VerifyAgent1217): - pass - - -class VerificationItem615(VerificationItem): - pass - - -class DeclaredBy616(DeclaredBy): - pass - - -class VerifyAgent1232(VerifyAgent): - pass - - -class VerifyAgent1233(VerifyAgent1217): - pass - - -class VerificationItem616(VerificationItem): - pass - - -class DeclaredBy617(DeclaredBy): - pass - - -class VerifyAgent1234(VerifyAgent): - pass - - -class VerifyAgent1235(VerifyAgent1217): - pass - - -class VerificationItem617(VerificationItem): - pass - - -class DeclaredBy618(DeclaredBy): - pass - - -class VerifyAgent1236(VerifyAgent): - pass - - -class VerifyAgent1237(VerifyAgent1217): - pass - - -class VerificationItem618(VerificationItem): - pass - - -class DeclaredBy619(DeclaredBy): - pass - - -class VerifyAgent1238(VerifyAgent): - pass - - -class VerifyAgent1239(VerifyAgent1217): - pass - - -class VerificationItem619(VerificationItem): - pass - - -class DeclaredBy620(DeclaredBy): - pass - - -class VerifyAgent1240(VerifyAgent): - pass - - -class VerifyAgent1241(VerifyAgent1217): - pass - - -class VerificationItem620(VerificationItem): - pass - - -class DeclaredBy621(DeclaredBy): - pass - - -class VerifyAgent1242(VerifyAgent): - pass - - -class VerifyAgent1243(VerifyAgent1217): - pass - - -class VerificationItem621(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role657(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role657, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy622(DeclaredBy): - pass - - -class VerifyAgent1244(VerifyAgent): - pass - - -class VerifyAgent1245(VerifyAgent1217): - pass - - -class VerificationItem622(VerificationItem): - pass - - -class DeclaredBy623(DeclaredBy): - pass - - -class VerifyAgent1246(VerifyAgent): - pass - - -class VerifyAgent1247(VerifyAgent1217): - pass - - -class VerificationItem623(VerificationItem): - pass - - -class DeclaredBy624(DeclaredBy): - pass - - -class VerifyAgent1248(VerifyAgent): - pass - - -class VerifyAgent1249(VerifyAgent1217): - pass - - -class VerificationItem624(VerificationItem): - pass - - -class DeclaredBy625(DeclaredBy): - pass - - -class VerifyAgent1250(VerifyAgent): - pass - - -class VerifyAgent1251(VerifyAgent1217): - pass - - -class VerificationItem625(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy626(DeclaredBy): - pass - - -class VerifyAgent1252(VerifyAgent): - pass - - -class VerifyAgent1253(VerifyAgent1217): - pass - - -class VerificationItem626(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy627(DeclaredBy): - pass - - -class VerifyAgent1254(VerifyAgent): - pass - - -class VerifyAgent1255(VerifyAgent1217): - pass - - -class VerificationItem627(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy628(DeclaredBy): - pass - - -class VerifyAgent1256(VerifyAgent): - pass - - -class VerifyAgent1257(VerifyAgent1217): - pass - - -class VerificationItem628(VerificationItem): - pass - - -class Target67(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy629(DeclaredBy): - pass - - -class VerifyAgent1258(VerifyAgent): - pass - - -class VerifyAgent1259(VerifyAgent1217): - pass - - -class VerificationItem629(VerificationItem): - pass - - -class DeclaredBy630(DeclaredBy): - pass - - -class VerifyAgent1260(VerifyAgent): - pass - - -class VerifyAgent1261(VerifyAgent1217): - pass - - -class VerificationItem630(VerificationItem): - pass - - -class DeclaredBy631(DeclaredBy): - pass - - -class VerifyAgent1262(VerifyAgent): - pass - - -class VerifyAgent1263(VerifyAgent1217): - pass - - -class VerificationItem631(VerificationItem): - pass - - -class DeclaredBy632(DeclaredBy): - pass - - -class VerifyAgent1264(VerifyAgent): - pass - - -class VerifyAgent1265(VerifyAgent1217): - pass - - -class VerificationItem632(VerificationItem): - pass - - -class DeclaredBy633(DeclaredBy): - pass - - -class VerifyAgent1266(VerifyAgent): - pass - - -class VerifyAgent1267(VerifyAgent1217): - pass - - -class VerificationItem633(VerificationItem): - pass - - -class DeclaredBy634(DeclaredBy): - pass - - -class VerifyAgent1268(VerifyAgent): - pass - - -class VerifyAgent1269(VerifyAgent1217): - pass - - -class VerificationItem634(VerificationItem): - pass - - -class DeclaredBy635(DeclaredBy): - pass - - -class VerifyAgent1270(VerifyAgent): - pass - - -class VerifyAgent1271(VerifyAgent1217): - pass - - -class VerificationItem635(VerificationItem): - pass - - -class DeclaredBy636(DeclaredBy): - pass - - -class VerifyAgent1272(VerifyAgent): - pass - - -class VerifyAgent1273(VerifyAgent1217): - pass - - -class VerificationItem636(VerificationItem): - pass - - -class DeclaredBy637(DeclaredBy): - pass - - -class VerifyAgent1274(VerifyAgent): - pass - - -class VerifyAgent1275(VerifyAgent1217): - pass - - -class VerificationItem637(VerificationItem): - pass - - -class DeclaredBy638(DeclaredBy): - pass - - -class VerifyAgent1276(VerifyAgent): - pass - - -class VerifyAgent1277(VerifyAgent1217): - pass - - -class VerificationItem638(VerificationItem): - pass - - -class DeclaredBy639(DeclaredBy): - pass - - -class VerifyAgent1278(VerifyAgent): - pass - - -class VerifyAgent1279(VerifyAgent1217): - pass - - -class VerificationItem639(VerificationItem): - pass - - -class DeclaredBy640(DeclaredBy): - pass - - -class VerifyAgent1280(VerifyAgent): - pass - - -class VerifyAgent1281(VerifyAgent1217): - pass - - -class VerificationItem640(VerificationItem): - pass - - -class DeclaredBy641(DeclaredBy): - pass - - -class VerifyAgent1282(VerifyAgent): - pass - - -class VerifyAgent1283(VerifyAgent1217): - pass - - -class VerificationItem641(VerificationItem): - pass - - -class DeclaredBy642(DeclaredBy): - pass - - -class VerifyAgent1284(VerifyAgent): - pass - - -class VerifyAgent1285(VerifyAgent1217): - pass - - -class VerificationItem642(VerificationItem): - pass - - -class DeclaredBy643(DeclaredBy): - pass - - -class VerifyAgent1286(VerifyAgent): - pass - - -class VerifyAgent1287(VerifyAgent1217): - pass - - -class VerificationItem643(VerificationItem): - pass - - -class ReferenceAsset24(ReferenceAsset): - pass - - -class FeedFieldMapping25(FeedFieldMapping): - pass - - -class ReferenceAuthorization23(ReferenceAuthorization): - pass - - -class DeclaredBy644(DeclaredBy): - pass - - -class VerifyAgent1288(VerifyAgent): - pass - - -class VerifyAgent1289(VerifyAgent1217): - pass - - -class VerificationItem644(VerificationItem): - pass - - -class DeclaredBy645(DeclaredBy): - pass - - -class VerifyAgent1290(VerifyAgent): - pass - - -class VerifyAgent1291(VerifyAgent1217): - pass - - -class VerificationItem645(VerificationItem): - pass - - -class DeclaredBy646(DeclaredBy): - pass - - -class VerifyAgent1292(VerifyAgent): - pass - - -class VerifyAgent1293(VerifyAgent1217): - pass - - -class VerificationItem646(VerificationItem): - pass - - -class DeclaredBy647(DeclaredBy): - pass - - -class VerifyAgent1294(VerifyAgent): - pass - - -class VerifyAgent1295(VerifyAgent1217): - pass - - -class VerificationItem647(VerificationItem): - pass - - -class DeclaredBy648(DeclaredBy): - pass - - -class VerifyAgent1296(VerifyAgent): - pass - - -class VerifyAgent1297(VerifyAgent1217): - pass - - -class VerificationItem648(VerificationItem): - pass - - -class DeclaredBy649(DeclaredBy): - pass - - -class VerifyAgent1298(VerifyAgent): - pass - - -class VerifyAgent1299(VerifyAgent1217): - pass - - -class VerificationItem649(VerificationItem): - pass - - -class DeclaredBy650(DeclaredBy): - pass - - -class VerifyAgent1300(VerifyAgent): - pass - - -class VerifyAgent1301(VerifyAgent1217): - pass - - -class VerificationItem650(VerificationItem): - pass - - -class DeclaredBy651(DeclaredBy): - pass - - -class VerifyAgent1302(VerifyAgent): - pass - - -class VerifyAgent1303(VerifyAgent1217): - pass - - -class VerificationItem651(VerificationItem): - pass - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DeclaredBy652(DeclaredBy): - pass - - -class VerifyAgent1304(VerifyAgent): - pass - - -class VerifyAgent1305(VerifyAgent1217): - pass - - -class VerificationItem652(VerificationItem): - pass - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ExcludedCountry(Country): - pass - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class DeclaredBy653(DeclaredBy): - pass - - -class VerifyAgent1306(VerifyAgent): - pass - - -class VerifyAgent1307(VerifyAgent1217): - pass - - -class VerificationItem653(VerificationItem): - pass - - - -class DeclaredBy654(DeclaredBy): - pass - - -class VerifyAgent1308(VerifyAgent): - pass - - -class VerifyAgent1309(VerifyAgent1217): - pass - - -class VerificationItem654(VerificationItem): - pass - - -class DeclaredBy655(DeclaredBy): - pass - - -class VerifyAgent1310(VerifyAgent): - pass - - -class VerifyAgent1311(VerifyAgent1217): - pass - - -class VerificationItem655(VerificationItem): - pass - - -class DeclaredBy656(DeclaredBy): - pass - - -class VerifyAgent1312(VerifyAgent): - pass - - -class VerifyAgent1313(VerifyAgent1217): - pass - - -class VerificationItem656(VerificationItem): - pass - - -class DeclaredBy657(DeclaredBy): - pass - - -class VerifyAgent1314(VerifyAgent): - pass - - -class VerifyAgent1315(VerifyAgent1217): - pass - - -class VerificationItem657(VerificationItem): - pass - - -class DeclaredBy658(DeclaredBy): - pass - - -class VerifyAgent1316(VerifyAgent): - pass - - -class VerifyAgent1317(VerifyAgent1217): - pass - - -class VerificationItem658(VerificationItem): - pass - - -class DeclaredBy659(DeclaredBy): - pass - - -class VerifyAgent1318(VerifyAgent): - pass - - -class VerifyAgent1319(VerifyAgent1217): - pass - - -class VerificationItem659(VerificationItem): - pass - - -class DeclaredBy660(DeclaredBy): - pass - - -class VerifyAgent1320(VerifyAgent): - pass - - -class VerifyAgent1321(VerifyAgent1217): - pass - - -class VerificationItem660(VerificationItem): - pass - - -class DeclaredBy661(DeclaredBy): - pass - - -class VerifyAgent1322(VerifyAgent): - pass - - -class VerifyAgent1323(VerifyAgent1217): - pass - - -class VerificationItem661(VerificationItem): - pass - - -class DeclaredBy662(DeclaredBy): - pass - - -class VerifyAgent1324(VerifyAgent): - pass - - -class VerifyAgent1325(VerifyAgent1217): - pass - - -class VerificationItem662(VerificationItem): - pass - - -class DeclaredBy663(DeclaredBy): - pass - - -class VerifyAgent1326(VerifyAgent): - pass - - -class VerifyAgent1327(VerifyAgent1217): - pass - - -class VerificationItem663(VerificationItem): - pass - - -class DeclaredBy664(DeclaredBy): - pass - - -class VerifyAgent1328(VerifyAgent): - pass - - -class VerifyAgent1329(VerifyAgent1217): - pass - - -class VerificationItem664(VerificationItem): - pass - - -class DeclaredBy665(DeclaredBy): - pass - - -class VerifyAgent1330(VerifyAgent): - pass - - -class VerifyAgent1331(VerifyAgent1217): - pass - - -class VerificationItem665(VerificationItem): - pass - - -class DeclaredBy666(DeclaredBy): - pass - - -class VerifyAgent1332(VerifyAgent): - pass - - -class VerifyAgent1333(VerifyAgent1217): - pass - - -class VerificationItem666(VerificationItem): - pass - - -class DeclaredBy667(DeclaredBy): - pass - - -class VerifyAgent1334(VerifyAgent): - pass - - -class VerifyAgent1335(VerifyAgent1217): - pass - - -class VerificationItem667(VerificationItem): - pass - - -class ReferenceAsset25(ReferenceAsset): - pass - - -class FeedFieldMapping26(FeedFieldMapping): - pass - - -class ReferenceAuthorization24(ReferenceAuthorization): - pass - - -class DeclaredBy668(DeclaredBy): - pass - - -class VerifyAgent1336(VerifyAgent): - pass - - -class VerifyAgent1337(VerifyAgent1217): - pass - - -class VerificationItem668(VerificationItem): - pass - - -class DeclaredBy669(DeclaredBy): - pass - - -class VerifyAgent1338(VerifyAgent): - pass - - -class VerifyAgent1339(VerifyAgent1217): - pass - - -class VerificationItem669(VerificationItem): - pass - - -class DeclaredBy670(DeclaredBy): - pass - - -class VerifyAgent1340(VerifyAgent): - pass - - -class VerifyAgent1341(VerifyAgent1217): - pass - - -class VerificationItem670(VerificationItem): - pass - - -class DeclaredBy671(DeclaredBy): - pass - - -class VerifyAgent1342(VerifyAgent): - pass - - -class VerifyAgent1343(VerifyAgent1217): - pass - - -class VerificationItem671(VerificationItem): - pass - - -class DeclaredBy672(DeclaredBy): - pass - - -class VerifyAgent1344(VerifyAgent): - pass - - -class VerifyAgent1345(VerifyAgent1217): - pass - - -class VerificationItem672(VerificationItem): - pass - - -class DeclaredBy673(DeclaredBy): - pass - - -class VerifyAgent1346(VerifyAgent): - pass - - -class VerifyAgent1347(VerifyAgent1217): - pass - - -class VerificationItem673(VerificationItem): - pass - - -class DeclaredBy674(DeclaredBy): - pass - - -class VerifyAgent1348(VerifyAgent): - pass - - -class VerifyAgent1349(VerifyAgent1217): - pass - - -class VerificationItem674(VerificationItem): - pass - - -class DeclaredBy675(DeclaredBy): - pass - - -class VerifyAgent1350(VerifyAgent): - pass - - -class VerifyAgent1351(VerifyAgent1217): - pass - - -class VerificationItem675(VerificationItem): - pass - - -class DeclaredBy676(DeclaredBy): - pass - - -class VerifyAgent1352(VerifyAgent): - pass - - -class VerifyAgent1353(VerifyAgent1217): - pass - - -class VerificationItem676(VerificationItem): - pass - - -class DeclaredBy677(DeclaredBy): - pass - - -class VerifyAgent1354(VerifyAgent): - pass - - -class VerifyAgent1355(VerifyAgent1217): - pass - - -class VerificationItem677(VerificationItem): - pass - - -class DeclaredBy678(DeclaredBy): - pass - - -class VerifyAgent1356(VerifyAgent): - pass - - -class VerifyAgent1357(VerifyAgent1217): - pass - - -class VerificationItem678(VerificationItem): - pass - - -class DeclaredBy679(DeclaredBy): - pass - - -class VerifyAgent1358(VerifyAgent): - pass - - -class VerifyAgent1359(VerifyAgent1217): - pass - - -class VerificationItem679(VerificationItem): - pass - - -class DeclaredBy680(DeclaredBy): - pass - - -class VerifyAgent1360(VerifyAgent): - pass - - -class VerifyAgent1361(VerifyAgent1217): - pass - - -class VerificationItem680(VerificationItem): - pass - - -class DeclaredBy681(DeclaredBy): - pass - - -class VerifyAgent1362(VerifyAgent): - pass - - -class VerifyAgent1363(VerifyAgent1217): - pass - - -class VerificationItem681(VerificationItem): - pass - - -class DeclaredBy682(DeclaredBy): - pass - - -class VerifyAgent1364(VerifyAgent): - pass - - -class VerifyAgent1365(VerifyAgent1217): - pass - - -class VerificationItem682(VerificationItem): - pass - - -class DeclaredBy683(DeclaredBy): - pass - - -class VerifyAgent1366(VerifyAgent): - pass - - -class VerifyAgent1367(VerifyAgent1217): - pass - - -class VerificationItem683(VerificationItem): - pass - - -class DeclaredBy684(DeclaredBy): - pass - - -class VerifyAgent1368(VerifyAgent): - pass - - -class VerifyAgent1369(VerifyAgent1217): - pass - - -class VerificationItem684(VerificationItem): - pass - - -class DeclaredBy685(DeclaredBy): - pass - - -class VerifyAgent1370(VerifyAgent): - pass - - -class VerifyAgent1371(VerifyAgent1217): - pass - - -class VerificationItem685(VerificationItem): - pass - - -class DeclaredBy686(DeclaredBy): - pass - - -class VerifyAgent1372(VerifyAgent): - pass - - -class VerifyAgent1373(VerifyAgent1217): - pass - - -class VerificationItem686(VerificationItem): - pass - - -class DeclaredBy687(DeclaredBy): - pass - - -class VerifyAgent1374(VerifyAgent): - pass - - -class VerifyAgent1375(VerifyAgent1217): - pass - - -class VerificationItem687(VerificationItem): - pass - - -class DeclaredBy688(DeclaredBy): - pass - - -class VerifyAgent1376(VerifyAgent): - pass - - -class VerifyAgent1377(VerifyAgent1217): - pass - - -class VerificationItem688(VerificationItem): - pass - - -class DeclaredBy689(DeclaredBy): - pass - - -class VerifyAgent1378(VerifyAgent): - pass - - -class VerifyAgent1379(VerifyAgent1217): - pass - - -class VerificationItem689(VerificationItem): - pass - - -class ReferenceAsset26(ReferenceAsset): - pass - - -class FeedFieldMapping27(FeedFieldMapping): - pass - - -class ReferenceAuthorization25(ReferenceAuthorization): - pass - - -class DeclaredBy690(DeclaredBy): - pass - - -class VerifyAgent1380(VerifyAgent): - pass - - -class VerifyAgent1381(VerifyAgent1217): - pass - - -class VerificationItem690(VerificationItem): - pass - - -class DeclaredBy691(DeclaredBy): - pass - - -class VerifyAgent1382(VerifyAgent): - pass - - -class VerifyAgent1383(VerifyAgent1217): - pass - - -class VerificationItem691(VerificationItem): - pass - - -class DeclaredBy692(DeclaredBy): - pass - - -class VerifyAgent1384(VerifyAgent): - pass - - -class VerifyAgent1385(VerifyAgent1217): - pass - - -class VerificationItem692(VerificationItem): - pass - - -class DeclaredBy693(DeclaredBy): - pass - - -class VerifyAgent1386(VerifyAgent): - pass - - -class VerifyAgent1387(VerifyAgent1217): - pass - - -class VerificationItem693(VerificationItem): - pass - - -class DeclaredBy694(DeclaredBy): - pass - - -class VerifyAgent1388(VerifyAgent): - pass - - -class VerifyAgent1389(VerifyAgent1217): - pass - - -class VerificationItem694(VerificationItem): - pass - - -class DeclaredBy695(DeclaredBy): - pass - - -class VerifyAgent1390(VerifyAgent): - pass - - -class VerifyAgent1391(VerifyAgent1217): - pass - - -class VerificationItem695(VerificationItem): - pass - - -class DeclaredBy696(DeclaredBy): - pass - - -class VerifyAgent1392(VerifyAgent): - pass - - -class VerifyAgent1393(VerifyAgent1217): - pass - - -class VerificationItem696(VerificationItem): - pass - - -class DeclaredBy697(DeclaredBy): - pass - - -class VerifyAgent1394(VerifyAgent): - pass - - -class VerifyAgent1395(VerifyAgent1217): - pass - - -class VerificationItem697(VerificationItem): - pass - - -class DeclaredBy698(DeclaredBy): - pass - - -class VerifyAgent1396(VerifyAgent): - pass - - -class VerifyAgent1397(VerifyAgent1217): - pass - - -class VerificationItem698(VerificationItem): - pass - - -class DeclaredBy699(DeclaredBy): - pass - - -class VerifyAgent1398(VerifyAgent): - pass - - -class VerifyAgent1399(VerifyAgent1217): - pass - - -class VerificationItem699(VerificationItem): - pass - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad', 'Desktop dark mode')" - ), - ] - macros: Annotated[ - dict[str, str] | None, - Field( - description="Macro values to use for this preview. Supports all universal macros from the format's supported_macros list." - ), - ] = None - context_description: Annotated[ - str | None, - Field( - description="Natural language description of the context for AI-generated content (e.g., 'User just searched for running shoes', 'Podcast discussing weather patterns')" - ), - ] = None - - - -class DeclaredBy700(DeclaredBy): - pass - - -class VerifyAgent1400(VerifyAgent): - pass - - -class VerifyAgent1401(VerifyAgent1217): - pass - - -class VerificationItem700(VerificationItem): - pass - - -class DeclaredBy701(DeclaredBy): - pass - - -class VerifyAgent1402(VerifyAgent): - pass - - -class VerifyAgent1403(VerifyAgent1217): - pass - - -class VerificationItem701(VerificationItem): - pass - - -class DeclaredBy702(DeclaredBy): - pass - - -class VerifyAgent1404(VerifyAgent): - pass - - -class VerifyAgent1405(VerifyAgent1217): - pass - - -class VerificationItem702(VerificationItem): - pass - - -class DeclaredBy703(DeclaredBy): - pass - - -class VerifyAgent1406(VerifyAgent): - pass - - -class VerifyAgent1407(VerifyAgent1217): - pass - - -class VerificationItem703(VerificationItem): - pass - - -class DeclaredBy704(DeclaredBy): - pass - - -class VerifyAgent1408(VerifyAgent): - pass - - -class VerifyAgent1409(VerifyAgent1217): - pass - - -class VerificationItem704(VerificationItem): - pass - - -class DeclaredBy705(DeclaredBy): - pass - - -class VerifyAgent1410(VerifyAgent): - pass - - -class VerifyAgent1411(VerifyAgent1217): - pass - - -class VerificationItem705(VerificationItem): - pass - - -class DeclaredBy706(DeclaredBy): - pass - - -class VerifyAgent1412(VerifyAgent): - pass - - -class VerifyAgent1413(VerifyAgent1217): - pass - - -class VerificationItem706(VerificationItem): - pass - - -class DeclaredBy707(DeclaredBy): - pass - - -class VerifyAgent1414(VerifyAgent): - pass - - -class VerifyAgent1415(VerifyAgent1217): - pass - - -class VerificationItem707(VerificationItem): - pass - - -class DeclaredBy708(DeclaredBy): - pass - - -class VerifyAgent1416(VerifyAgent): - pass - - -class VerifyAgent1417(VerifyAgent1217): - pass - - -class VerificationItem708(VerificationItem): - pass - - -class DeclaredBy709(DeclaredBy): - pass - - -class VerifyAgent1418(VerifyAgent): - pass - - -class VerifyAgent1419(VerifyAgent1217): - pass - - -class VerificationItem709(VerificationItem): - pass - - -class DeclaredBy710(DeclaredBy): - pass - - -class VerifyAgent1420(VerifyAgent): - pass - - -class VerifyAgent1421(VerifyAgent1217): - pass - - -class VerificationItem710(VerificationItem): - pass - - -class DeclaredBy711(DeclaredBy): - pass - - -class VerifyAgent1422(VerifyAgent): - pass - - -class VerifyAgent1423(VerifyAgent1217): - pass - - -class VerificationItem711(VerificationItem): - pass - - -class DeclaredBy712(DeclaredBy): - pass - - -class VerifyAgent1424(VerifyAgent): - pass - - -class VerifyAgent1425(VerifyAgent1217): - pass - - -class VerificationItem712(VerificationItem): - pass - - -class DeclaredBy713(DeclaredBy): - pass - - -class VerifyAgent1426(VerifyAgent): - pass - - -class VerifyAgent1427(VerifyAgent1217): - pass - - -class VerificationItem713(VerificationItem): - pass - - -class ReferenceAsset27(ReferenceAsset): - pass - - -class FeedFieldMapping28(FeedFieldMapping): - pass - - -class ReferenceAuthorization26(ReferenceAuthorization): - pass - - -class DeclaredBy714(DeclaredBy): - pass - - -class VerifyAgent1428(VerifyAgent): - pass - - -class VerifyAgent1429(VerifyAgent1217): - pass - - -class VerificationItem714(VerificationItem): - pass - - -class DeclaredBy715(DeclaredBy): - pass - - -class VerifyAgent1430(VerifyAgent): - pass - - -class VerifyAgent1431(VerifyAgent1217): - pass - - -class VerificationItem715(VerificationItem): - pass - - -class DeclaredBy716(DeclaredBy): - pass - - -class VerifyAgent1432(VerifyAgent): - pass - - -class VerifyAgent1433(VerifyAgent1217): - pass - - -class VerificationItem716(VerificationItem): - pass - - -class DeclaredBy717(DeclaredBy): - pass - - -class VerifyAgent1434(VerifyAgent): - pass - - -class VerifyAgent1435(VerifyAgent1217): - pass - - -class VerificationItem717(VerificationItem): - pass - - -class DeclaredBy718(DeclaredBy): - pass - - -class VerifyAgent1436(VerifyAgent): - pass - - -class VerifyAgent1437(VerifyAgent1217): - pass - - -class VerificationItem718(VerificationItem): - pass - - -class DeclaredBy719(DeclaredBy): - pass - - -class VerifyAgent1438(VerifyAgent): - pass - - -class VerifyAgent1439(VerifyAgent1217): - pass - - -class VerificationItem719(VerificationItem): - pass - - -class DeclaredBy720(DeclaredBy): - pass - - -class VerifyAgent1440(VerifyAgent): - pass - - -class VerifyAgent1441(VerifyAgent1217): - pass - - -class VerificationItem720(VerificationItem): - pass - - -class DeclaredBy721(DeclaredBy): - pass - - -class VerifyAgent1442(VerifyAgent): - pass - - -class VerifyAgent1443(VerifyAgent1217): - pass - - -class VerificationItem721(VerificationItem): - pass - - -class DeclaredBy722(DeclaredBy): - pass - - -class VerifyAgent1444(VerifyAgent): - pass - - -class VerifyAgent1445(VerifyAgent1217): - pass - - -class VerificationItem722(VerificationItem): - pass - - -class DeclaredBy723(DeclaredBy): - pass - - -class VerifyAgent1446(VerifyAgent): - pass - - -class VerifyAgent1447(VerifyAgent1217): - pass - - -class VerificationItem723(VerificationItem): - pass - - -class DeclaredBy724(DeclaredBy): - pass - - -class VerifyAgent1448(VerifyAgent): - pass - - -class VerifyAgent1449(VerifyAgent1217): - pass - - -class VerificationItem724(VerificationItem): - pass - - -class DeclaredBy725(DeclaredBy): - pass - - -class VerifyAgent1450(VerifyAgent): - pass - - -class VerifyAgent1451(VerifyAgent1217): - pass - - -class VerificationItem725(VerificationItem): - pass - - -class DeclaredBy726(DeclaredBy): - pass - - -class VerifyAgent1452(VerifyAgent): - pass - - -class VerifyAgent1453(VerifyAgent1217): - pass - - -class VerificationItem726(VerificationItem): - pass - - -class DeclaredBy727(DeclaredBy): - pass - - -class VerifyAgent1454(VerifyAgent): - pass - - -class VerifyAgent1455(VerifyAgent1217): - pass - - -class VerificationItem727(VerificationItem): - pass - - -class DeclaredBy728(DeclaredBy): - pass - - -class VerifyAgent1456(VerifyAgent): - pass - - -class VerifyAgent1457(VerifyAgent1217): - pass - - -class VerificationItem728(VerificationItem): - pass - - -class DeclaredBy729(DeclaredBy): - pass - - -class VerifyAgent1458(VerifyAgent): - pass - - -class VerifyAgent1459(VerifyAgent1217): - pass - - -class VerificationItem729(VerificationItem): - pass - - -class DeclaredBy730(DeclaredBy): - pass - - -class VerifyAgent1460(VerifyAgent): - pass - - -class VerifyAgent1461(VerifyAgent1217): - pass - - -class VerificationItem730(VerificationItem): - pass - - -class DeclaredBy731(DeclaredBy): - pass - - -class VerifyAgent1462(VerifyAgent): - pass - - -class VerifyAgent1463(VerifyAgent1217): - pass - - -class VerificationItem731(VerificationItem): - pass - - -class DeclaredBy732(DeclaredBy): - pass - - -class VerifyAgent1464(VerifyAgent): - pass - - -class VerifyAgent1465(VerifyAgent1217): - pass - - -class VerificationItem732(VerificationItem): - pass - - -class DeclaredBy733(DeclaredBy): - pass - - -class VerifyAgent1466(VerifyAgent): - pass - - -class VerifyAgent1467(VerifyAgent1217): - pass - - -class VerificationItem733(VerificationItem): - pass - - -class DeclaredBy734(DeclaredBy): - pass - - -class VerifyAgent1468(VerifyAgent): - pass - - -class VerifyAgent1469(VerifyAgent1217): - pass - - -class VerificationItem734(VerificationItem): - pass - - -class DeclaredBy735(DeclaredBy): - pass - - -class VerifyAgent1470(VerifyAgent): - pass - - -class VerifyAgent1471(VerifyAgent1217): - pass - - -class VerificationItem735(VerificationItem): - pass - - -class ReferenceAsset28(ReferenceAsset): - pass - - -class FeedFieldMapping29(FeedFieldMapping): - pass - - -class ReferenceAuthorization27(ReferenceAuthorization): - pass - - -class DeclaredBy736(DeclaredBy): - pass - - -class VerifyAgent1472(VerifyAgent): - pass - - -class VerifyAgent1473(VerifyAgent1217): - pass - - -class VerificationItem736(VerificationItem): - pass - - -class DeclaredBy737(DeclaredBy): - pass - - -class VerifyAgent1474(VerifyAgent): - pass - - -class VerifyAgent1475(VerifyAgent1217): - pass - - -class VerificationItem737(VerificationItem): - pass - - -class DeclaredBy738(DeclaredBy): - pass - - -class VerifyAgent1476(VerifyAgent): - pass - - -class VerifyAgent1477(VerifyAgent1217): - pass - - -class VerificationItem738(VerificationItem): - pass - - -class DeclaredBy739(DeclaredBy): - pass - - -class VerifyAgent1478(VerifyAgent): - pass - - -class VerifyAgent1479(VerifyAgent1217): - pass - - -class VerificationItem739(VerificationItem): - pass - - -class DeclaredBy740(DeclaredBy): - pass - - -class VerifyAgent1480(VerifyAgent): - pass - - -class VerifyAgent1481(VerifyAgent1217): - pass - - -class VerificationItem740(VerificationItem): - pass - - -class DeclaredBy741(DeclaredBy): - pass - - -class VerifyAgent1482(VerifyAgent): - pass - - -class VerifyAgent1483(VerifyAgent1217): - pass - - -class VerificationItem741(VerificationItem): - pass - - -class DeclaredBy742(DeclaredBy): - pass - - -class VerifyAgent1484(VerifyAgent): - pass - - -class VerifyAgent1485(VerifyAgent1217): - pass - - -class VerificationItem742(VerificationItem): - pass - - -class DeclaredBy743(DeclaredBy): - pass - - -class VerifyAgent1486(VerifyAgent): - pass - - -class VerifyAgent1487(VerifyAgent1217): - pass - - -class VerificationItem743(VerificationItem): - pass - - -class DeclaredBy744(DeclaredBy): - pass - - -class VerifyAgent1488(VerifyAgent): - pass - - -class VerifyAgent1489(VerifyAgent1217): - pass - - -class VerificationItem744(VerificationItem): - pass - - -class DeclaredBy745(DeclaredBy): - pass - - -class VerifyAgent1490(VerifyAgent): - pass - - -class VerifyAgent1491(VerifyAgent1217): - pass - - -class VerificationItem745(VerificationItem): - pass - - - -class DeclaredBy746(DeclaredBy): - pass - - -class VerifyAgent1492(VerifyAgent): - pass - - -class VerifyAgent1493(VerifyAgent1217): - pass - - -class VerificationItem746(VerificationItem): - pass - - -class DeclaredBy747(DeclaredBy): - pass - - -class VerifyAgent1494(VerifyAgent): - pass - - -class VerifyAgent1495(VerifyAgent1217): - pass - - -class VerificationItem747(VerificationItem): - pass - - -class DeclaredBy748(DeclaredBy): - pass - - -class VerifyAgent1496(VerifyAgent): - pass - - -class VerifyAgent1497(VerifyAgent1217): - pass - - -class VerificationItem748(VerificationItem): - pass - - -class DeclaredBy749(DeclaredBy): - pass - - -class VerifyAgent1498(VerifyAgent): - pass - - -class VerifyAgent1499(VerifyAgent1217): - pass - - -class VerificationItem749(VerificationItem): - pass - - -class DeclaredBy750(DeclaredBy): - pass - - -class VerifyAgent1500(VerifyAgent): - pass - - -class VerifyAgent1501(VerifyAgent1217): - pass - - -class VerificationItem750(VerificationItem): - pass - - -class DeclaredBy751(DeclaredBy): - pass - - -class VerifyAgent1502(VerifyAgent): - pass - - -class VerifyAgent1503(VerifyAgent1217): - pass - - -class VerificationItem751(VerificationItem): - pass - - -class DeclaredBy752(DeclaredBy): - pass - - -class VerifyAgent1504(VerifyAgent): - pass - - -class VerifyAgent1505(VerifyAgent1217): - pass - - -class VerificationItem752(VerificationItem): - pass - - -class DeclaredBy753(DeclaredBy): - pass - - -class VerifyAgent1506(VerifyAgent): - pass - - -class VerifyAgent1507(VerifyAgent1217): - pass - - -class VerificationItem753(VerificationItem): - pass - - -class DeclaredBy754(DeclaredBy): - pass - - -class VerifyAgent1508(VerifyAgent): - pass - - -class VerifyAgent1509(VerifyAgent1217): - pass - - -class VerificationItem754(VerificationItem): - pass - - -class DeclaredBy755(DeclaredBy): - pass - - -class VerifyAgent1510(VerifyAgent): - pass - - -class VerifyAgent1511(VerifyAgent1217): - pass - - -class VerificationItem755(VerificationItem): - pass - - -class DeclaredBy756(DeclaredBy): - pass - - -class VerifyAgent1512(VerifyAgent): - pass - - -class VerifyAgent1513(VerifyAgent1217): - pass - - -class VerificationItem756(VerificationItem): - pass - - -class DeclaredBy757(DeclaredBy): - pass - - -class VerifyAgent1514(VerifyAgent): - pass - - -class VerifyAgent1515(VerifyAgent1217): - pass - - -class VerificationItem757(VerificationItem): - pass - - -class DeclaredBy758(DeclaredBy): - pass - - -class VerifyAgent1516(VerifyAgent): - pass - - -class VerifyAgent1517(VerifyAgent1217): - pass - - -class VerificationItem758(VerificationItem): - pass - - -class DeclaredBy759(DeclaredBy): - pass - - -class VerifyAgent1518(VerifyAgent): - pass - - -class VerifyAgent1519(VerifyAgent1217): - pass - - -class VerificationItem759(VerificationItem): - pass - - -class ReferenceAsset29(ReferenceAsset): - pass - - -class FeedFieldMapping30(FeedFieldMapping): - pass - - -class ReferenceAuthorization28(ReferenceAuthorization): - pass - - -class DeclaredBy760(DeclaredBy): - pass - - -class VerifyAgent1520(VerifyAgent): - pass - - -class VerifyAgent1521(VerifyAgent1217): - pass - - -class VerificationItem760(VerificationItem): - pass - - -class DeclaredBy761(DeclaredBy): - pass - - -class VerifyAgent1522(VerifyAgent): - pass - - -class VerifyAgent1523(VerifyAgent1217): - pass - - -class VerificationItem761(VerificationItem): - pass - - -class DeclaredBy762(DeclaredBy): - pass - - -class VerifyAgent1524(VerifyAgent): - pass - - -class VerifyAgent1525(VerifyAgent1217): - pass - - -class VerificationItem762(VerificationItem): - pass - - -class DeclaredBy763(DeclaredBy): - pass - - -class VerifyAgent1526(VerifyAgent): - pass - - -class VerifyAgent1527(VerifyAgent1217): - pass - - -class VerificationItem763(VerificationItem): - pass - - -class DeclaredBy764(DeclaredBy): - pass - - -class VerifyAgent1528(VerifyAgent): - pass - - -class VerifyAgent1529(VerifyAgent1217): - pass - - -class VerificationItem764(VerificationItem): - pass - - -class DeclaredBy765(DeclaredBy): - pass - - -class VerifyAgent1530(VerifyAgent): - pass - - -class VerifyAgent1531(VerifyAgent1217): - pass - - -class VerificationItem765(VerificationItem): - pass - - -class DeclaredBy766(DeclaredBy): - pass - - -class VerifyAgent1532(VerifyAgent): - pass - - -class VerifyAgent1533(VerifyAgent1217): - pass - - -class VerificationItem766(VerificationItem): - pass - - -class DeclaredBy767(DeclaredBy): - pass - - -class VerifyAgent1534(VerifyAgent): - pass - - -class VerifyAgent1535(VerifyAgent1217): - pass - - -class VerificationItem767(VerificationItem): - pass - - -class DeclaredBy768(DeclaredBy): - pass - - -class VerifyAgent1536(VerifyAgent): - pass - - -class VerifyAgent1537(VerifyAgent1217): - pass - - -class VerificationItem768(VerificationItem): - pass - - -class DeclaredBy769(DeclaredBy): - pass - - -class VerifyAgent1538(VerifyAgent): - pass - - -class VerifyAgent1539(VerifyAgent1217): - pass - - -class VerificationItem769(VerificationItem): - pass - - -class DeclaredBy770(DeclaredBy): - pass - - -class VerifyAgent1540(VerifyAgent): - pass - - -class VerifyAgent1541(VerifyAgent1217): - pass - - -class VerificationItem770(VerificationItem): - pass - - -class DeclaredBy771(DeclaredBy): - pass - - -class VerifyAgent1542(VerifyAgent): - pass - - -class VerifyAgent1543(VerifyAgent1217): - pass - - -class VerificationItem771(VerificationItem): - pass - - -class DeclaredBy772(DeclaredBy): - pass - - -class VerifyAgent1544(VerifyAgent): - pass - - -class VerifyAgent1545(VerifyAgent1217): - pass - - -class VerificationItem772(VerificationItem): - pass - - -class DeclaredBy773(DeclaredBy): - pass - - -class VerifyAgent1546(VerifyAgent): - pass - - -class VerifyAgent1547(VerifyAgent1217): - pass - - -class VerificationItem773(VerificationItem): - pass - - -class DeclaredBy774(DeclaredBy): - pass - - -class VerifyAgent1548(VerifyAgent): - pass - - -class VerifyAgent1549(VerifyAgent1217): - pass - - -class VerificationItem774(VerificationItem): - pass - - -class DeclaredBy775(DeclaredBy): - pass - - -class VerifyAgent1550(VerifyAgent): - pass - - -class VerifyAgent1551(VerifyAgent1217): - pass - - -class VerificationItem775(VerificationItem): - pass - - -class DeclaredBy776(DeclaredBy): - pass - - -class VerifyAgent1552(VerifyAgent): - pass - - -class VerifyAgent1553(VerifyAgent1217): - pass - - -class VerificationItem776(VerificationItem): - pass - - -class DeclaredBy777(DeclaredBy): - pass - - -class VerifyAgent1554(VerifyAgent): - pass - - -class VerifyAgent1555(VerifyAgent1217): - pass - - -class VerificationItem777(VerificationItem): - pass - - -class DeclaredBy778(DeclaredBy): - pass - - -class VerifyAgent1556(VerifyAgent): - pass - - -class VerifyAgent1557(VerifyAgent1217): - pass - - -class VerificationItem778(VerificationItem): - pass - - -class DeclaredBy779(DeclaredBy): - pass - - -class VerifyAgent1558(VerifyAgent): - pass - - -class VerifyAgent1559(VerifyAgent1217): - pass - - -class VerificationItem779(VerificationItem): - pass - - -class DeclaredBy780(DeclaredBy): - pass - - -class VerifyAgent1560(VerifyAgent): - pass - - -class VerifyAgent1561(VerifyAgent1217): - pass - - -class VerificationItem780(VerificationItem): - pass - - -class DeclaredBy781(DeclaredBy): - pass - - -class VerifyAgent1562(VerifyAgent): - pass - - -class VerifyAgent1563(VerifyAgent1217): - pass - - -class VerificationItem781(VerificationItem): - pass - - -class ReferenceAsset30(ReferenceAsset): - pass - - -class FeedFieldMapping31(FeedFieldMapping): - pass - - -class ReferenceAuthorization29(ReferenceAuthorization): - pass - - -class DeclaredBy782(DeclaredBy): - pass - - -class VerifyAgent1564(VerifyAgent): - pass - - -class VerifyAgent1565(VerifyAgent1217): - pass - - -class VerificationItem782(VerificationItem): - pass - - -class DeclaredBy783(DeclaredBy): - pass - - -class VerifyAgent1566(VerifyAgent): - pass - - -class VerifyAgent1567(VerifyAgent1217): - pass - - -class VerificationItem783(VerificationItem): - pass - - -class DeclaredBy784(DeclaredBy): - pass - - -class VerifyAgent1568(VerifyAgent): - pass - - -class VerifyAgent1569(VerifyAgent1217): - pass - - -class VerificationItem784(VerificationItem): - pass - - -class DeclaredBy785(DeclaredBy): - pass - - -class VerifyAgent1570(VerifyAgent): - pass - - -class VerifyAgent1571(VerifyAgent1217): - pass - - -class VerificationItem785(VerificationItem): - pass - - -class DeclaredBy786(DeclaredBy): - pass - - -class VerifyAgent1572(VerifyAgent): - pass - - -class VerifyAgent1573(VerifyAgent1217): - pass - - -class VerificationItem786(VerificationItem): - pass - - -class DeclaredBy787(DeclaredBy): - pass - - -class VerifyAgent1574(VerifyAgent): - pass - - -class VerifyAgent1575(VerifyAgent1217): - pass - - -class VerificationItem787(VerificationItem): - pass - - -class DeclaredBy788(DeclaredBy): - pass - - -class VerifyAgent1576(VerifyAgent): - pass - - -class VerifyAgent1577(VerifyAgent1217): - pass - - -class VerificationItem788(VerificationItem): - pass - - -class DeclaredBy789(DeclaredBy): - pass - - -class VerifyAgent1578(VerifyAgent): - pass - - -class VerifyAgent1579(VerifyAgent1217): - pass - - -class VerificationItem789(VerificationItem): - pass - - -class DeclaredBy790(DeclaredBy): - pass - - -class VerifyAgent1580(VerifyAgent): - pass - - -class VerifyAgent1581(VerifyAgent1217): - pass - - -class VerificationItem790(VerificationItem): - pass - - -class DeclaredBy791(DeclaredBy): - pass - - -class VerifyAgent1582(VerifyAgent): - pass - - -class VerifyAgent1583(VerifyAgent1217): - pass - - -class VerificationItem791(VerificationItem): - pass - - -class Input7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this input set')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to use for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class RightUse(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class CreativeQuality(StrEnum): - draft = 'draft' - production = 'production' - - -class PreviewOutputFormat(StrEnum): - url = 'url' - html = 'html' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1217 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction633] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1218 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1219 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction634(Jurisdiction633): - pass - - -class Disclosure611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction634] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy609 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem609] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark609] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure611 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem609] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance609 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1220 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1221 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction635(Jurisdiction633): - pass - - -class Disclosure612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction635] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy610 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem610] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark610] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure612 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem610] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance610 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1222 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1223 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction636(Jurisdiction633): - pass - - -class Disclosure613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction636] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy611 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem611] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark611] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure613 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem611] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance611 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1224 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1225 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction637(Jurisdiction633): - pass - - -class Disclosure614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction637] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy612 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem612] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark612] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure614 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem612] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance612 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1226 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1227 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction638(Jurisdiction633): - pass - - -class Disclosure615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction638] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy613 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem613] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark613] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure615 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem613] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets343(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance613 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1228 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1229 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction639(Jurisdiction633): - pass - - -class Disclosure616(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction639] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy614 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem614] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark614] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure616 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem614] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets344(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance614 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1230 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1231 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction640(Jurisdiction633): - pass - - -class Disclosure617(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction640] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy615 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem615] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark615] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure617 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem615] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets345(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance615 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem616(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1232 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark616(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1233 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction641(Jurisdiction633): - pass - - -class Disclosure618(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction641] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance616(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy616 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem616] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark616] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure618 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem616] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets346(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance616 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem617(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1234 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark617(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1235 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction642(Jurisdiction633): - pass - - -class Disclosure619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction642] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance617(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy617 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem617] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark617] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure619 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem617] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets347(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance617 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem618(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1236 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark618(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1237 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction643(Jurisdiction633): - pass - - -class Disclosure620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction643] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance618(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy618 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem618] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark618] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure620 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem618] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets348(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance618 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1238 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1239 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction644(Jurisdiction633): - pass - - -class Disclosure621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction644] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy619 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem619] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark619] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure621 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem619] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets349(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance619 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1240 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1241 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction645(Jurisdiction633): - pass - - -class Disclosure622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction645] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy620 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem620] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark620] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure622 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem620] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets350(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance620 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1242 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1243 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction646(Jurisdiction633): - pass - - -class Disclosure623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction646] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy621 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem621] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark621] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure623 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem621] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets351(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance621 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets352(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets353(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets354(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1244 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1245 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction648(Jurisdiction633): - pass - - -class Disclosure624(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction648] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy622 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem622] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark622] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure624 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem622] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets355(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance622 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1246 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1247 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction649(Jurisdiction633): - pass - - -class Disclosure625(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction649] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy623 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem623] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark623] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure625 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem623] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance623 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem624(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1248 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark624(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1249 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction650(Jurisdiction633): - pass - - -class Disclosure626(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction650] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance624(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy624 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem624] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark624] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure626 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem624] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance624 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem625(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1250 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark625(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1251 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction651(Jurisdiction633): - pass - - -class Disclosure627(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction651] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance625(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy625 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem625] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark625] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure627 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem625] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance625 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem626(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1252 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark626(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1253 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction652(Jurisdiction633): - pass - - -class Disclosure628(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction652] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance626(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy626 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem626] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark626] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure628 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem626] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets356(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media45, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance626 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem627(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1254 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark627(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1255 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction653(Jurisdiction633): - pass - - -class Disclosure629(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction653] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance627(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy627 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem627] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark627] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure629 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem627] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets357(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance627 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem628(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1256 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark628(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1257 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction654(Jurisdiction633): - pass - - -class Disclosure630(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction654] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance628(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy628 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem628] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark628] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure630 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem628] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets358(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance628 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem629(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1258 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark629(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1259 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction655(Jurisdiction633): - pass - - -class Disclosure631(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction655] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance629(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy629 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem629] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark629] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure631 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem629] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets359(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance629 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem630(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1260 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark630(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1261 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction656(Jurisdiction633): - pass - - -class Disclosure632(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction656] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance630(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy630 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem630] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark630] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure632 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem630] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance630 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem631(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1262 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark631(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1263 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction657(Jurisdiction633): - pass - - -class Disclosure633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction657] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance631(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy631 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem631] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark631] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure633 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem631] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3603(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance631 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem632(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1264 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark632(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1265 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction658(Jurisdiction633): - pass - - -class Disclosure634(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction658] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance632(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy632 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem632] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark632] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure634 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem632] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3604(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance632 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1266 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1267 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction659(Jurisdiction633): - pass - - -class Disclosure635(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction659] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy633 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem633] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark633] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure635 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem633] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3605(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance633 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem634(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1268 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark634(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1269 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction660(Jurisdiction633): - pass - - -class Disclosure636(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction660] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance634(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy634 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem634] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark634] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure636 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem634] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3606(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance634 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem635(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1270 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark635(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1271 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction661(Jurisdiction633): - pass - - -class Disclosure637(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction661] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance635(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy635 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem635] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark635] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure637 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem635] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3607(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance635 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem636(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1272 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark636(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1273 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction662(Jurisdiction633): - pass - - -class Disclosure638(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction662] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance636(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy636 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem636] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark636] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure638 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem636] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3608(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance636 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem637(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1274 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark637(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1275 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction663(Jurisdiction633): - pass - - -class Disclosure639(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction663] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance637(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy637 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem637] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark637] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure639 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem637] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance637 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem638(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1276 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark638(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1277 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction664(Jurisdiction633): - pass - - -class Disclosure640(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction664] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance638(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy638 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem638] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark638] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure640 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem638] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance638 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem639(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1278 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark639(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1279 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction665(Jurisdiction633): - pass - - -class Disclosure641(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction665] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance639(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy639 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem639] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark639] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure641 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem639] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance639 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem640(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1280 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark640(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1281 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction666(Jurisdiction633): - pass - - -class Disclosure642(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction666] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance640(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy640 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem640] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark640] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure642 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem640] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance640 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem641(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1282 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark641(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1283 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction667(Jurisdiction633): - pass - - -class Disclosure643(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction667] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance641(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy641 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem641] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark641] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure643 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem641] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance641 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem642(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1284 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark642(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1285 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction668(Jurisdiction633): - pass - - -class Disclosure644(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction668] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance642(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy642 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem642] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark642] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure644 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem642] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance642 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem643(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1286 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark643(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1287 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction669(Jurisdiction633): - pass - - -class Disclosure645(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction669] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance643(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy643 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem643] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark643] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure645 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem643] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance643 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure24(RequiredDisclosure): - pass - - -class Compliance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure24] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets36017(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset24] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance24 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets36018(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping25] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem644(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1288 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark644(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1289 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction671(Jurisdiction633): - pass - - -class Disclosure646(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction671] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance644(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy644 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem644] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark644] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure646 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem644] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization23 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance644 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem645(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1290 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark645(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1291 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction672(Jurisdiction633): - pass - - -class Disclosure647(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction672] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance645(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy645 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem645] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark645] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure647 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem645] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance645 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem646(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1292 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark646(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1293 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction673(Jurisdiction633): - pass - - -class Disclosure648(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction673] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance646(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy646 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem646] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark646] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure648 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem646] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance646 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem647(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1294 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark647(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1295 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction674(Jurisdiction633): - pass - - -class Disclosure649(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction674] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance647(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy647 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem647] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark647] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure649 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem647] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance647 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem648(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1296 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark648(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1297 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction675(Jurisdiction633): - pass - - -class Disclosure650(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction675] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance648(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy648 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem648] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark648] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure650 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem648] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media46 | Media47, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl23 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance648 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem649(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1298 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark649(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1299 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction676(Jurisdiction633): - pass - - -class Disclosure651(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction676] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance649(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy649 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem649] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark649] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure651 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem649] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance649 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem650(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1300 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark650(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1301 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction677(Jurisdiction633): - pass - - -class Disclosure652(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction677] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance650(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy650 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem650] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark650] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure652 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem650] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance650 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem651(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1302 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark651(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1303 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction678(Jurisdiction633): - pass - - -class Disclosure653(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction678] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance651(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy651 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem651] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark651] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure653 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem651] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance651 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets3601( - RootModel[ - Assets3602 - | Assets3603 - | Assets3604 - | Assets3605 - | Assets3606 - | Assets3607 - | Assets3608 - | Assets3609 - | Assets36010 - | Assets36011 - | Assets36012 - | Assets36013 - | Assets36014 - | Assets36015 - | Assets352 - | Assets36017 - | Assets36018 - | Assets36019 - | Assets36020 - | Assets36021 - | Assets36022 - | Assets36023 - ] -): - root: Annotated[ - Assets3602 - | Assets3603 - | Assets3604 - | Assets3605 - | Assets3606 - | Assets3607 - | Assets3608 - | Assets3609 - | Assets36010 - | Assets36011 - | Assets36012 - | Assets36013 - | Assets36014 - | Assets36015 - | Assets352 - | Assets36017 - | Assets36018 - | Assets36019 - | Assets36020 - | Assets36021 - | Assets36022 - | Assets36023, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets360(RootModel[list[Assets3601]]): - root: Annotated[list[Assets3601], Field(min_length=1)] - - -class EmbeddedProvenanceItem652(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1304 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark652(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1305 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction679(Jurisdiction633): - pass - - -class Disclosure654(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction679] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance652(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy652 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem652] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark652] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure654 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem652] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance652 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[RightUse], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: RightType | None = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: CreativeIdentifierType - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class EmbeddedProvenanceItem653(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1306 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark653(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1307 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction680(Jurisdiction633): - pass - - -class Disclosure655(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction680] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance653(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy653 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem653] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark653] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure655 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem653] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef21 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets339 - | Assets340 - | Assets341 - | Assets342 - | Assets343 - | Assets344 - | Assets345 - | Assets346 - | Assets347 - | Assets348 - | Assets349 - | Assets350 - | Assets351 - | Assets352 - | Assets353 - | Assets354 - | Assets355 - | Assets356 - | Assets357 - | Assets358 - | Assets359 - | Assets360, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance653 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem654(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1308 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark654(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1309 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction681(Jurisdiction633): - pass - - -class Disclosure656(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction681] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance654(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy654 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem654] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark654] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure656 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem654] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets361(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance654 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem655(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1310 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark655(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1311 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction682(Jurisdiction633): - pass - - -class Disclosure657(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction682] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance655(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy655 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem655] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark655] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure657 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem655] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets362(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance655 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem656(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1312 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark656(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1313 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction683(Jurisdiction633): - pass - - -class Disclosure658(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction683] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance656(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy656 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem656] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark656] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure658 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem656] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets363(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance656 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem657(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1314 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark657(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1315 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction684(Jurisdiction633): - pass - - -class Disclosure659(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction684] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance657(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy657 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem657] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark657] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure659 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem657] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets364(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance657 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem658(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1316 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark658(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1317 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction685(Jurisdiction633): - pass - - -class Disclosure660(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction685] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance658(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy658 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem658] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark658] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure660 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem658] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets365(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance658 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem659(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1318 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark659(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1319 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction686(Jurisdiction633): - pass - - -class Disclosure661(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction686] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance659(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy659 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem659] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark659] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure661 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem659] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets366(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance659 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem660(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1320 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark660(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1321 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction687(Jurisdiction633): - pass - - -class Disclosure662(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction687] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance660(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy660 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem660] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark660] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure662 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem660] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets367(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance660 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem661(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1322 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark661(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1323 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction688(Jurisdiction633): - pass - - -class Disclosure663(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction688] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance661(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy661 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem661] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark661] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure663 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem661] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets368(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance661 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem662(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1324 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark662(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1325 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction689(Jurisdiction633): - pass - - -class Disclosure664(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction689] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance662(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy662 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem662] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark662] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure664 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem662] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets369(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance662 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem663(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1326 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark663(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1327 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction690(Jurisdiction633): - pass - - -class Disclosure665(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction690] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance663(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy663 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem663] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark663] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure665 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem663] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets370(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance663 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem664(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1328 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark664(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1329 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction691(Jurisdiction633): - pass - - -class Disclosure666(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction691] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance664(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy664 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem664] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark664] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure666 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem664] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets371(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance664 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem665(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1330 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark665(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1331 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction692(Jurisdiction633): - pass - - -class Disclosure667(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction692] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance665(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy665 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem665] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark665] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure667 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem665] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance665 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem666(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1332 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark666(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1333 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction693(Jurisdiction633): - pass - - -class Disclosure668(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction693] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance666(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy666 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem666] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark666] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure668 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem666] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance666 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem667(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1334 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark667(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1335 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction694(Jurisdiction633): - pass - - -class Disclosure669(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction694] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance667(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy667 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem667] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark667] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure669 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem667] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance667 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets375(Assets352): - pass - - -class RequiredDisclosure25(RequiredDisclosure): - pass - - -class Compliance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure25] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets376(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset25] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance25 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets377(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping26] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem668(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1336 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark668(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1337 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction696(Jurisdiction633): - pass - - -class Disclosure670(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction696] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance668(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy668 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem668] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark668] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure670 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem668] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization24 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance668 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem669(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1338 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark669(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1339 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction697(Jurisdiction633): - pass - - -class Disclosure671(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction697] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance669(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy669 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem669] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark669] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure671 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem669] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance669 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem670(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1340 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark670(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1341 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction698(Jurisdiction633): - pass - - -class Disclosure672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction698] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance670(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy670 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem670] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark670] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure672 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem670] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance670 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem671(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1342 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark671(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1343 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction699(Jurisdiction633): - pass - - -class Disclosure673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction699] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance671(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy671 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem671] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark671] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure673 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem671] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance671 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1344 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1345 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction700(Jurisdiction633): - pass - - -class Disclosure674(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction700] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy672 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem672] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark672] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure674 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem672] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media48 | Media49, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl24 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance672 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1346 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1347 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction701(Jurisdiction633): - pass - - -class Disclosure675(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction701] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy673 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem673] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark673] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure675 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem673] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets380(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance673 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem674(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1348 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark674(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1349 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction702(Jurisdiction633): - pass - - -class Disclosure676(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction702] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance674(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy674 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem674] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark674] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure676 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem674] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets381(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance674 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem675(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1350 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark675(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1351 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction703(Jurisdiction633): - pass - - -class Disclosure677(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction703] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance675(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy675 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem675] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark675] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure677 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem675] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets382(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance675 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem676(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1352 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark676(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1353 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction704(Jurisdiction633): - pass - - -class Disclosure678(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction704] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance676(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy676 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem676] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark676] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure678 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem676] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance676 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem677(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1354 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark677(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1355 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction705(Jurisdiction633): - pass - - -class Disclosure679(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction705] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance677(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy677 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem677] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark677] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure679 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem677] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance677 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem678(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1356 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark678(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1357 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction706(Jurisdiction633): - pass - - -class Disclosure680(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction706] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance678(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy678 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem678] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark678] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure680 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem678] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance678 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem679(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1358 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark679(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1359 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction707(Jurisdiction633): - pass - - -class Disclosure681(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction707] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance679(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy679 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem679] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark679] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure681 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem679] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance679 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem680(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1360 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark680(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1361 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction708(Jurisdiction633): - pass - - -class Disclosure682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction708] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance680(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy680 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem680] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark680] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure682 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem680] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance680 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem681(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1362 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark681(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1363 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction709(Jurisdiction633): - pass - - -class Disclosure683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction709] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance681(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy681 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem681] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark681] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure683 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem681] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance681 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1364 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1365 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction710(Jurisdiction633): - pass - - -class Disclosure684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction710] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy682 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem682] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark682] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure684 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem682] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance682 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1366 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1367 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction711(Jurisdiction633): - pass - - -class Disclosure685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction711] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy683 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem683] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark683] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure685 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem683] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance683 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1368 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1369 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction712(Jurisdiction633): - pass - - -class Disclosure686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction712] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy684 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem684] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark684] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure686 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem684] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance684 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1370 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1371 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction713(Jurisdiction633): - pass - - -class Disclosure687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction713] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy685 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem685] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark685] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure687 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem685] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance685 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1372 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1373 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction714(Jurisdiction633): - pass - - -class Disclosure688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction714] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy686 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem686] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark686] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure688 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem686] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance686 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1374 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1375 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction715(Jurisdiction633): - pass - - -class Disclosure689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction715] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy687 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem687] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark687] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure689 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem687] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance687 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1376 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1377 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction716(Jurisdiction633): - pass - - -class Disclosure690(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction716] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy688 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem688] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark688] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure690 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem688] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance688 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1378 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1379 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction717(Jurisdiction633): - pass - - -class Disclosure691(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction717] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy689 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem689] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark689] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure691 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem689] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance689 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure26(RequiredDisclosure): - pass - - -class Compliance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure26] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets38317(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset26] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance26 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets38318(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping27] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem690(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1380 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark690(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1381 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction719(Jurisdiction633): - pass - - -class Disclosure692(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction719] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance690(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy690 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem690] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark690] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure692 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem690] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization25 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance690 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem691(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1382 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark691(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1383 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction720(Jurisdiction633): - pass - - -class Disclosure693(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction720] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance691(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy691 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem691] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark691] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure693 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem691] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance691 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem692(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1384 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark692(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1385 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction721(Jurisdiction633): - pass - - -class Disclosure694(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction721] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance692(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy692 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem692] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark692] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure694 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem692] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance692 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem693(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1386 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark693(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1387 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction722(Jurisdiction633): - pass - - -class Disclosure695(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction722] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance693(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy693 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem693] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark693] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure695 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem693] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance693 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem694(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1388 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark694(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1389 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction723(Jurisdiction633): - pass - - -class Disclosure696(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction723] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance694(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy694 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem694] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark694] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure696 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem694] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media50 | Media51, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl25 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance694 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem695(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1390 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark695(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1391 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction724(Jurisdiction633): - pass - - -class Disclosure697(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction724] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance695(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy695 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem695] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark695] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure697 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem695] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance695 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem696(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1392 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark696(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1393 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction725(Jurisdiction633): - pass - - -class Disclosure698(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction725] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance696(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy696 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem696] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark696] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure698 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem696] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance696 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem697(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1394 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark697(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1395 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction726(Jurisdiction633): - pass - - -class Disclosure699(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction726] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance697(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy697 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem697] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark697] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure699 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem697] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets38323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance697 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets3831( - RootModel[ - Assets3832 - | Assets3833 - | Assets3834 - | Assets3835 - | Assets3836 - | Assets3837 - | Assets3838 - | Assets3839 - | Assets38310 - | Assets38311 - | Assets38312 - | Assets38313 - | Assets38314 - | Assets38315 - | Assets352 - | Assets38317 - | Assets38318 - | Assets38319 - | Assets38320 - | Assets38321 - | Assets38322 - | Assets38323 - ] -): - root: Annotated[ - Assets3832 - | Assets3833 - | Assets3834 - | Assets3835 - | Assets3836 - | Assets3837 - | Assets3838 - | Assets3839 - | Assets38310 - | Assets38311 - | Assets38312 - | Assets38313 - | Assets38314 - | Assets38315 - | Assets352 - | Assets38317 - | Assets38318 - | Assets38319 - | Assets38320 - | Assets38321 - | Assets38322 - | Assets38323, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets383(RootModel[list[Assets3831]]): - root: Annotated[list[Assets3831], Field(min_length=1)] - - -class EmbeddedProvenanceItem698(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1396 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark698(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1397 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction727(Jurisdiction633): - pass - - -class Disclosure700(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction727] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance698(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy698 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem698] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark698] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure700 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem698] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance698 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo89 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand39(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride89 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right14(Right): - pass - - -class EmbeddedProvenanceItem699(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1398 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark699(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1399 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction728(Jurisdiction633): - pass - - -class Disclosure701(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction728] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance699(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy699 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem699] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark699] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure701 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem699] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef21 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets361 - | Assets362 - | Assets363 - | Assets364 - | Assets365 - | Assets366 - | Assets367 - | Assets368 - | Assets369 - | Assets370 - | Assets371 - | Assets372 - | Assets373 - | Assets374 - | Assets375 - | Assets376 - | Assets377 - | Assets378 - | Assets379 - | Assets380 - | Assets381 - | Assets382 - | Assets383, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand39 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right14] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance699 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem700(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1400 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark700(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1401 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction729(Jurisdiction633): - pass - - -class Disclosure702(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction729] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance700(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy700 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem700] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark700] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure702 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem700] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets384(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance700 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem701(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1402 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark701(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1403 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction730(Jurisdiction633): - pass - - -class Disclosure703(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction730] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance701(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy701 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem701] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark701] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure703 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem701] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets385(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance701 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem702(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1404 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark702(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1405 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction731(Jurisdiction633): - pass - - -class Disclosure704(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction731] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance702(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy702 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem702] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark702] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure704 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem702] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets386(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance702 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem703(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1406 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark703(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1407 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction732(Jurisdiction633): - pass - - -class Disclosure705(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction732] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance703(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy703 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem703] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark703] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure705 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem703] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets387(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance703 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem704(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1408 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark704(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1409 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction733(Jurisdiction633): - pass - - -class Disclosure706(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction733] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance704(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy704 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem704] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark704] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure706 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem704] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets388(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance704 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem705(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1410 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark705(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1411 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction734(Jurisdiction633): - pass - - -class Disclosure707(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction734] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance705(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy705 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem705] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark705] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure707 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem705] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets389(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance705 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem706(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1412 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark706(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1413 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction735(Jurisdiction633): - pass - - -class Disclosure708(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction735] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance706(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy706 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem706] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark706] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure708 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem706] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets390(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance706 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem707(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1414 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark707(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1415 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction736(Jurisdiction633): - pass - - -class Disclosure709(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction736] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance707(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy707 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem707] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark707] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure709 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem707] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance707 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem708(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1416 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark708(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1417 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction737(Jurisdiction633): - pass - - -class Disclosure710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction737] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance708(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy708 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem708] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark708] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure710 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem708] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets392(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance708 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem709(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1418 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark709(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1419 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction738(Jurisdiction633): - pass - - -class Disclosure711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction738] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance709(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy709 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem709] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark709] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure711 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem709] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets393(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance709 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1420 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1421 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction739(Jurisdiction633): - pass - - -class Disclosure712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction739] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy710 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem710] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark710] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure712 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem710] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets394(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance710 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1422 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1423 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction740(Jurisdiction633): - pass - - -class Disclosure713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction740] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy711 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem711] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark711] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure713 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem711] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets395(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance711 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1424 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1425 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction741(Jurisdiction633): - pass - - -class Disclosure714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction741] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy712 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem712] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark712] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure714 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem712] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets396(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance712 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1426 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1427 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction742(Jurisdiction633): - pass - - -class Disclosure715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction742] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy713 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem713] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark713] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure715 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem713] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets397(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance713 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets398(Assets352): - pass - - -class RequiredDisclosure27(RequiredDisclosure): - pass - - -class Compliance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure27] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets399(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset27] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance27 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets400(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping28] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1428 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1429 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction744(Jurisdiction633): - pass - - -class Disclosure716(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction744] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy714 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem714] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark714] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure716 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem714] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets401(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization26 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance714 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1430 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1431 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction745(Jurisdiction633): - pass - - -class Disclosure717(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction745] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy715 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem715] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark715] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure717 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem715] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance715 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem716(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1432 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark716(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1433 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction746(Jurisdiction633): - pass - - -class Disclosure718(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction746] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance716(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy716 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem716] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark716] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure718 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem716] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance716 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem717(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1434 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark717(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1435 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction747(Jurisdiction633): - pass - - -class Disclosure719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction747] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance717(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy717 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem717] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark717] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure719 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem717] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance717 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem718(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1436 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark718(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1437 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction748(Jurisdiction633): - pass - - -class Disclosure720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction748] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance718(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy718 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem718] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark718] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure720 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem718] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets402(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media52 | Media53, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl26 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance718 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1438 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1439 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction749(Jurisdiction633): - pass - - -class Disclosure721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction749] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy719 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem719] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark719] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure721 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem719] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets403(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance719 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1440 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1441 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction750(Jurisdiction633): - pass - - -class Disclosure722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction750] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy720 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem720] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark720] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure722 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem720] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets404(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance720 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1442 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1443 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction751(Jurisdiction633): - pass - - -class Disclosure723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction751] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy721 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem721] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark721] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure723 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem721] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets405(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance721 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1444 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1445 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction752(Jurisdiction633): - pass - - -class Disclosure724(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction752] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy722 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem722] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark722] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure724 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem722] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4062(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance722 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1446 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1447 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction753(Jurisdiction633): - pass - - -class Disclosure725(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction753] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy723 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem723] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark723] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure725 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem723] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4063(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance723 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem724(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1448 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark724(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1449 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction754(Jurisdiction633): - pass - - -class Disclosure726(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction754] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance724(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy724 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem724] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark724] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure726 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem724] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4064(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance724 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem725(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1450 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark725(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1451 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction755(Jurisdiction633): - pass - - -class Disclosure727(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction755] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance725(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy725 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem725] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark725] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure727 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem725] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4065(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance725 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem726(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1452 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark726(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1453 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction756(Jurisdiction633): - pass - - -class Disclosure728(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction756] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance726(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy726 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem726] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark726] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure728 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem726] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4066(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance726 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem727(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1454 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark727(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1455 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction757(Jurisdiction633): - pass - - -class Disclosure729(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction757] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance727(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy727 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem727] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark727] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure729 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem727] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4067(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance727 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem728(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1456 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark728(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1457 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction758(Jurisdiction633): - pass - - -class Disclosure730(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction758] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance728(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy728 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem728] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark728] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure730 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem728] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4068(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance728 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem729(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1458 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark729(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1459 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction759(Jurisdiction633): - pass - - -class Disclosure731(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction759] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance729(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy729 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem729] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark729] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure731 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem729] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4069(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance729 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem730(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1460 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark730(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1461 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction760(Jurisdiction633): - pass - - -class Disclosure732(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction760] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance730(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy730 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem730] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark730] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure732 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem730] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40610(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance730 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem731(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1462 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark731(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1463 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction761(Jurisdiction633): - pass - - -class Disclosure733(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction761] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance731(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy731 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem731] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark731] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure733 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem731] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40611(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance731 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem732(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1464 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark732(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1465 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction762(Jurisdiction633): - pass - - -class Disclosure734(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction762] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance732(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy732 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem732] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark732] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure734 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem732] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40612(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance732 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem733(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1466 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark733(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1467 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction763(Jurisdiction633): - pass - - -class Disclosure735(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction763] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance733(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy733 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem733] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark733] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure735 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem733] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40613(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance733 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem734(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1468 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark734(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1469 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction764(Jurisdiction633): - pass - - -class Disclosure736(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction764] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance734(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy734 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem734] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark734] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure736 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem734] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40614(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance734 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem735(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1470 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark735(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1471 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction765(Jurisdiction633): - pass - - -class Disclosure737(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction765] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance735(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy735 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem735] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark735] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure737 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem735] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40615(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance735 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure28(RequiredDisclosure): - pass - - -class Compliance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure28] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets40617(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset28] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance28 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets40618(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping29] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem736(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1472 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark736(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1473 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction767(Jurisdiction633): - pass - - -class Disclosure738(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction767] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance736(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy736 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem736] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark736] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure738 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem736] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40619(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization27 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance736 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem737(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1474 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark737(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1475 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction768(Jurisdiction633): - pass - - -class Disclosure739(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction768] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance737(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy737 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem737] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark737] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure739 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem737] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance737 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem738(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1476 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark738(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1477 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction769(Jurisdiction633): - pass - - -class Disclosure740(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction769] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance738(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy738 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem738] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark738] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure740 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem738] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance738 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem739(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1478 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark739(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1479 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction770(Jurisdiction633): - pass - - -class Disclosure741(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction770] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance739(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy739 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem739] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark739] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure741 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem739] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance739 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem740(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1480 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark740(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1481 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction771(Jurisdiction633): - pass - - -class Disclosure742(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction771] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance740(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy740 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem740] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark740] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure742 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem740] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40620(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media54 | Media55, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl27 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance740 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem741(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1482 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark741(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1483 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction772(Jurisdiction633): - pass - - -class Disclosure743(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction772] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance741(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy741 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem741] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark741] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure743 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem741] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40621(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance741 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem742(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1484 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark742(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1485 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction773(Jurisdiction633): - pass - - -class Disclosure744(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction773] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance742(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy742 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem742] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark742] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure744 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem742] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40622(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance742 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem743(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1486 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark743(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1487 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction774(Jurisdiction633): - pass - - -class Disclosure745(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction774] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance743(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy743 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem743] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark743] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure745 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem743] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40623(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance743 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets4061( - RootModel[ - Assets4062 - | Assets4063 - | Assets4064 - | Assets4065 - | Assets4066 - | Assets4067 - | Assets4068 - | Assets4069 - | Assets40610 - | Assets40611 - | Assets40612 - | Assets40613 - | Assets40614 - | Assets40615 - | Assets352 - | Assets40617 - | Assets40618 - | Assets40619 - | Assets40620 - | Assets40621 - | Assets40622 - | Assets40623 - ] -): - root: Annotated[ - Assets4062 - | Assets4063 - | Assets4064 - | Assets4065 - | Assets4066 - | Assets4067 - | Assets4068 - | Assets4069 - | Assets40610 - | Assets40611 - | Assets40612 - | Assets40613 - | Assets40614 - | Assets40615 - | Assets352 - | Assets40617 - | Assets40618 - | Assets40619 - | Assets40620 - | Assets40621 - | Assets40622 - | Assets40623, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets406(RootModel[list[Assets4061]]): - root: Annotated[list[Assets4061], Field(min_length=1)] - - -class EmbeddedProvenanceItem744(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1488 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark744(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1489 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction775(Jurisdiction633): - pass - - -class Disclosure746(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction775] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance744(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy744 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem744] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark744] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure746 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem744] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance744 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo90 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand40(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride90 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right15(Right): - pass - - -class EmbeddedProvenanceItem745(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1490 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark745(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1491 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction776(Jurisdiction633): - pass - - -class Disclosure747(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction776] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance745(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy745 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem745] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark745] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure747 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem745] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef21 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets384 - | Assets385 - | Assets386 - | Assets387 - | Assets388 - | Assets389 - | Assets390 - | Assets391 - | Assets392 - | Assets393 - | Assets394 - | Assets395 - | Assets396 - | Assets397 - | Assets398 - | Assets399 - | Assets400 - | Assets401 - | Assets402 - | Assets403 - | Assets404 - | Assets405 - | Assets406, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand40 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right15] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance745 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem746(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1492 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark746(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1493 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction777(Jurisdiction633): - pass - - -class Disclosure748(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction777] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance746(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy746 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem746] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark746] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure748 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem746] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets407(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance746 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem747(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1494 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark747(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1495 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction778(Jurisdiction633): - pass - - -class Disclosure749(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction778] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance747(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy747 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem747] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark747] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure749 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem747] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets408(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance747 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem748(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1496 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark748(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1497 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction779(Jurisdiction633): - pass - - -class Disclosure750(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction779] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance748(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy748 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem748] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark748] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure750 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem748] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets409(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance748 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem749(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1498 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark749(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1499 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction780(Jurisdiction633): - pass - - -class Disclosure751(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction780] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance749(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy749 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem749] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark749] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure751 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem749] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance749 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem750(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1500 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark750(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1501 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction781(Jurisdiction633): - pass - - -class Disclosure752(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction781] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance750(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy750 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem750] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark750] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure752 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem750] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance750 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem751(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1502 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark751(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1503 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction782(Jurisdiction633): - pass - - -class Disclosure753(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction782] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance751(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy751 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem751] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark751] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure753 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem751] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance751 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem752(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1504 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark752(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1505 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction783(Jurisdiction633): - pass - - -class Disclosure754(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction783] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance752(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy752 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem752] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark752] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure754 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem752] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance752 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem753(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1506 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark753(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1507 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction784(Jurisdiction633): - pass - - -class Disclosure755(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction784] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance753(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy753 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem753] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark753] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure755 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem753] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance753 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem754(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1508 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark754(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1509 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction785(Jurisdiction633): - pass - - -class Disclosure756(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction785] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance754(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy754 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem754] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark754] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure756 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem754] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance754 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem755(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1510 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark755(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1511 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction786(Jurisdiction633): - pass - - -class Disclosure757(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction786] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance755(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy755 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem755] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark755] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure757 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem755] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets416(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance755 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem756(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1512 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark756(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1513 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction787(Jurisdiction633): - pass - - -class Disclosure758(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction787] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance756(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy756 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem756] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark756] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure758 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem756] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets417(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance756 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem757(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1514 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark757(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1515 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction788(Jurisdiction633): - pass - - -class Disclosure759(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction788] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance757(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy757 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem757] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark757] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure759 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem757] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets418(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance757 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem758(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1516 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark758(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1517 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction789(Jurisdiction633): - pass - - -class Disclosure760(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction789] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance758(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy758 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem758] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark758] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure760 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem758] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance758 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem759(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1518 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark759(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1519 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction790(Jurisdiction633): - pass - - -class Disclosure761(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction790] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance759(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy759 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem759] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark759] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure761 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem759] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance759 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets421(Assets352): - pass - - -class RequiredDisclosure29(RequiredDisclosure): - pass - - -class Compliance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure29] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets422(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset29] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance29 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets423(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping30] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem760(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1520 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark760(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1521 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction792(Jurisdiction633): - pass - - -class Disclosure762(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction792] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance760(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy760 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem760] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark760] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure762 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem760] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets424(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization28 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance760 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem761(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1522 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark761(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1523 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction793(Jurisdiction633): - pass - - -class Disclosure763(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction793] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance761(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy761 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem761] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark761] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure763 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem761] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance761 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem762(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1524 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark762(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1525 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction794(Jurisdiction633): - pass - - -class Disclosure764(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction794] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance762(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy762 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem762] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark762] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure764 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem762] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance762 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem763(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1526 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark763(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1527 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction795(Jurisdiction633): - pass - - -class Disclosure765(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction795] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance763(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy763 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem763] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark763] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure765 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem763] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance763 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem764(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1528 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark764(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1529 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction796(Jurisdiction633): - pass - - -class Disclosure766(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction796] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance764(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy764 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem764] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark764] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure766 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem764] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets425(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media56 | Media57, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl28 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance764 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem765(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1530 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark765(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1531 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction797(Jurisdiction633): - pass - - -class Disclosure767(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction797] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance765(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy765 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem765] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark765] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure767 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem765] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets426(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance765 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem766(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1532 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark766(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1533 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction798(Jurisdiction633): - pass - - -class Disclosure768(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction798] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance766(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy766 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem766] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark766] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure768 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem766] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets427(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance766 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem767(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1534 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark767(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1535 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction799(Jurisdiction633): - pass - - -class Disclosure769(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction799] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance767(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy767 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem767] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark767] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure769 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem767] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets428(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance767 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem768(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1536 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark768(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1537 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction800(Jurisdiction633): - pass - - -class Disclosure770(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction800] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance768(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy768 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem768] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark768] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure770 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem768] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance768 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem769(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1538 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark769(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1539 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction801(Jurisdiction633): - pass - - -class Disclosure771(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction801] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance769(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy769 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem769] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark769] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure771 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem769] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance769 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem770(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1540 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark770(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1541 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction802(Jurisdiction633): - pass - - -class Disclosure772(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction802] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance770(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy770 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem770] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark770] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure772 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem770] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance770 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem771(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1542 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark771(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1543 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction803(Jurisdiction633): - pass - - -class Disclosure773(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction803] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance771(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy771 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem771] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark771] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure773 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem771] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance771 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem772(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1544 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark772(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1545 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction804(Jurisdiction633): - pass - - -class Disclosure774(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction804] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance772(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy772 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem772] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark772] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure774 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem772] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance772 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem773(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1546 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark773(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1547 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction805(Jurisdiction633): - pass - - -class Disclosure775(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction805] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance773(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy773 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem773] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark773] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure775 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem773] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance773 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem774(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1548 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark774(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1549 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction806(Jurisdiction633): - pass - - -class Disclosure776(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction806] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance774(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy774 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem774] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark774] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure776 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem774] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance774 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem775(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1550 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark775(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1551 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction807(Jurisdiction633): - pass - - -class Disclosure777(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction807] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance775(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy775 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem775] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark775] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure777 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem775] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance775 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem776(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1552 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark776(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1553 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction808(Jurisdiction633): - pass - - -class Disclosure778(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction808] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance776(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy776 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem776] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark776] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure778 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem776] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42910(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance776 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem777(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1554 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark777(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1555 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction809(Jurisdiction633): - pass - - -class Disclosure779(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction809] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance777(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy777 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem777] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark777] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure779 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem777] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42911(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance777 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem778(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1556 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark778(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1557 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction810(Jurisdiction633): - pass - - -class Disclosure780(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction810] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance778(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy778 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem778] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark778] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure780 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem778] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance778 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem779(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1558 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark779(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1559 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction811(Jurisdiction633): - pass - - -class Disclosure781(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction811] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance779(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy779 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem779] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark779] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure781 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem779] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance779 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem780(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1560 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark780(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1561 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction812(Jurisdiction633): - pass - - -class Disclosure782(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction812] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance780(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy780 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem780] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark780] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure782 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem780] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance780 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem781(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1562 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark781(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1563 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction813(Jurisdiction633): - pass - - -class Disclosure783(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction813] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance781(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy781 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem781] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark781] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure783 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem781] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance781 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure30(RequiredDisclosure): - pass - - -class Compliance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure30] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets42917(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset30] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance30 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets42918(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping31] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem782(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1564 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark782(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1565 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction815(Jurisdiction633): - pass - - -class Disclosure784(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction815] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance782(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy782 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem782] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark782] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure784 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem782] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization29 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance782 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem783(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1566 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark783(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1567 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction816(Jurisdiction633): - pass - - -class Disclosure785(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction816] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance783(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy783 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem783] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark783] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure785 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem783] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance783 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem784(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1568 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark784(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1569 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction817(Jurisdiction633): - pass - - -class Disclosure786(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction817] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance784(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy784 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem784] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark784] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure786 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem784] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance784 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem785(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1570 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark785(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1571 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction818(Jurisdiction633): - pass - - -class Disclosure787(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction818] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance785(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy785 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem785] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark785] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure787 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem785] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance785 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem786(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1572 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark786(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1573 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction819(Jurisdiction633): - pass - - -class Disclosure788(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction819] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance786(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy786 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem786] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark786] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure788 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem786] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42920(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media58 | Media59, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl29 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance786 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem787(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1574 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark787(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1575 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction820(Jurisdiction633): - pass - - -class Disclosure789(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction820] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance787(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy787 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem787] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark787] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure789 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem787] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42921(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance787 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem788(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1576 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark788(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1577 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction821(Jurisdiction633): - pass - - -class Disclosure790(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction821] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance788(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy788 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem788] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark788] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure790 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem788] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42922(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance788 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem789(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1578 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark789(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1579 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction822(Jurisdiction633): - pass - - -class Disclosure791(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction822] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance789(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy789 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem789] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark789] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure791 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem789] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42923(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target67 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target67.linear - provenance: Annotated[ - Provenance789 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets4291( - RootModel[ - Assets4292 - | Assets4293 - | Assets4294 - | Assets4295 - | Assets4296 - | Assets4297 - | Assets4298 - | Assets4299 - | Assets42910 - | Assets42911 - | Assets42912 - | Assets42913 - | Assets42914 - | Assets42915 - | Assets352 - | Assets42917 - | Assets42918 - | Assets42919 - | Assets42920 - | Assets42921 - | Assets42922 - | Assets42923 - ] -): - root: Annotated[ - Assets4292 - | Assets4293 - | Assets4294 - | Assets4295 - | Assets4296 - | Assets4297 - | Assets4298 - | Assets4299 - | Assets42910 - | Assets42911 - | Assets42912 - | Assets42913 - | Assets42914 - | Assets42915 - | Assets352 - | Assets42917 - | Assets42918 - | Assets42919 - | Assets42920 - | Assets42921 - | Assets42922 - | Assets42923, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets429(RootModel[list[Assets4291]]): - root: Annotated[list[Assets4291], Field(min_length=1)] - - -class EmbeddedProvenanceItem790(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1580 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark790(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1581 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction823(Jurisdiction633): - pass - - -class Disclosure792(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction823] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance790(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy790 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem790] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark790] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure792 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem790] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance790 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo91 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand41(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride91 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right16(Right): - pass - - -class EmbeddedProvenanceItem791(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1582 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark791(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1583 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction824(Jurisdiction633): - pass - - -class Disclosure793(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction824] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance791(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy791 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem791] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark791] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure793 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem791] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef21 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets407 - | Assets408 - | Assets409 - | Assets410 - | Assets411 - | Assets412 - | Assets413 - | Assets414 - | Assets415 - | Assets416 - | Assets417 - | Assets418 - | Assets419 - | Assets420 - | Assets421 - | Assets422 - | Assets423 - | Assets424 - | Assets425 - | Assets426 - | Assets427 - | Assets428 - | Assets429, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand41 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right16] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance791 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Request(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description='Format identifier for rendering the preview. Defaults to creative_manifest.format_id if omitted.', - title='Format Reference (Structured Object)', - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest11 | CreativeManifest12, - Field( - description='Complete creative manifest with all required assets.', - title='Creative Manifest', - ), - ] - inputs: Annotated[ - list[Input7] | None, - Field( - description='Array of input sets for generating multiple preview variants', min_length=1 - ), - ] = None - template_id: Annotated[ - str | None, Field(description='Specific template ID for custom format rendering') - ] = None - quality: CreativeQuality | None = None - output_format: PreviewOutputFormat | None = PreviewOutputFormat.url - item_limit: Annotated[ - int | None, - Field(description='Maximum number of catalog items to render in this preview.', ge=1), - ] = None - - -class PreviewCreativeRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - request_type: Annotated[ - RequestType, - Field( - description="Preview mode. 'single' previews one creative manifest. 'batch' previews multiple creatives in one call. 'variant' replays a post-flight variant by ID." - ), - ] - creative_manifest: Annotated[ - CreativeManifest | CreativeManifest10 | None, - Field( - description="Complete creative manifest with all required assets for the format. Required when request_type is 'single'. Also accepted per item in batch mode.", - title='Creative Manifest', - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Format identifier for rendering the preview. Defaults to creative_manifest.format_id if omitted. Used in single mode.', - title='Format Reference (Structured Object)', - ), - ] = None - inputs: Annotated[ - list[Input] | None, - Field( - description='Array of input sets for generating multiple preview variants. Each input set defines macros and context values for one preview rendering. Used in single mode.', - min_length=1, - ), - ] = None - template_id: Annotated[ - str | None, - Field(description='Specific template ID for custom format rendering. Used in single mode.'), - ] = None - quality: CreativeQuality | None = None - output_format: PreviewOutputFormat | None = PreviewOutputFormat.url - item_limit: Annotated[ - int | None, - Field( - description='Maximum number of catalog items to render per preview variant. Used in single mode. Creative agents SHOULD default to a reasonable sample when omitted and the catalog is large.', - ge=1, - ), - ] = None - requests: Annotated[ - list[Request] | None, - Field( - description="Array of preview requests (1-50 items). Required when request_type is 'batch'. Each item follows the single request structure.", - max_length=50, - min_length=1, - ), - ] = None - variant_id: Annotated[ - str | None, - Field( - description="Platform-assigned variant identifier from get_creative_delivery response. Required when request_type is 'variant'." - ), - ] = None - creative_id: Annotated[ - str | None, Field(description='Creative identifier for context. Used in variant mode.') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/preview_creative_response.py b/src/adcp/types/generated_poc/bundled/creative/preview_creative_response.py deleted file mode 100644 index 38f5719e..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/preview_creative_response.py +++ /dev/null @@ -1,649 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/preview_creative_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class PreviewCreativeResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' diff --git a/src/adcp/types/generated_poc/bundled/creative/sync_creatives_request.py b/src/adcp/types/generated_poc/bundled/creative/sync_creatives_request.py deleted file mode 100644 index 62830f58..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/sync_creatives_request.py +++ /dev/null @@ -1,20903 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/sync_creatives_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1597(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class DeclaredBy805(DeclaredBy): - pass - - -class VerifyAgent1598(VerifyAgent): - pass - - -class VerifyAgent1599(VerifyAgent1597): - pass - - -class VerificationItem799(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy806(DeclaredBy): - pass - - -class VerifyAgent1600(VerifyAgent): - pass - - -class VerifyAgent1601(VerifyAgent1597): - pass - - -class VerificationItem800(VerificationItem): - pass - - -class DeclaredBy807(DeclaredBy): - pass - - -class VerifyAgent1602(VerifyAgent): - pass - - -class VerifyAgent1603(VerifyAgent1597): - pass - - -class VerificationItem801(VerificationItem): - pass - - -class DeclaredBy808(DeclaredBy): - pass - - -class VerifyAgent1604(VerifyAgent): - pass - - -class VerifyAgent1605(VerifyAgent1597): - pass - - -class VerificationItem802(VerificationItem): - pass - - -class DeclaredBy809(DeclaredBy): - pass - - -class VerifyAgent1606(VerifyAgent): - pass - - -class VerifyAgent1607(VerifyAgent1597): - pass - - -class VerificationItem803(VerificationItem): - pass - - -class DeclaredBy810(DeclaredBy): - pass - - -class VerifyAgent1608(VerifyAgent): - pass - - -class VerifyAgent1609(VerifyAgent1597): - pass - - -class VerificationItem804(VerificationItem): - pass - - -class DeclaredBy811(DeclaredBy): - pass - - -class VerifyAgent1610(VerifyAgent): - pass - - -class VerifyAgent1611(VerifyAgent1597): - pass - - -class VerificationItem805(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy812(DeclaredBy): - pass - - -class VerifyAgent1612(VerifyAgent): - pass - - -class VerifyAgent1613(VerifyAgent1597): - pass - - -class VerificationItem806(VerificationItem): - pass - - -class DeclaredBy813(DeclaredBy): - pass - - -class VerifyAgent1614(VerifyAgent): - pass - - -class VerifyAgent1615(VerifyAgent1597): - pass - - -class VerificationItem807(VerificationItem): - pass - - -class DeclaredBy814(DeclaredBy): - pass - - -class VerifyAgent1616(VerifyAgent): - pass - - -class VerifyAgent1617(VerifyAgent1597): - pass - - -class VerificationItem808(VerificationItem): - pass - - -class DeclaredBy815(DeclaredBy): - pass - - -class VerifyAgent1618(VerifyAgent): - pass - - -class VerifyAgent1619(VerifyAgent1597): - pass - - -class VerificationItem809(VerificationItem): - pass - - -class DeclaredBy816(DeclaredBy): - pass - - -class VerifyAgent1620(VerifyAgent): - pass - - -class VerifyAgent1621(VerifyAgent1597): - pass - - -class VerificationItem810(VerificationItem): - pass - - -class DeclaredBy817(DeclaredBy): - pass - - -class VerifyAgent1622(VerifyAgent): - pass - - -class VerifyAgent1623(VerifyAgent1597): - pass - - -class VerificationItem811(VerificationItem): - pass - - -class DeclaredBy818(DeclaredBy): - pass - - -class VerifyAgent1624(VerifyAgent): - pass - - -class VerifyAgent1625(VerifyAgent1597): - pass - - -class VerificationItem812(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role863(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role863, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy819(DeclaredBy): - pass - - -class VerifyAgent1626(VerifyAgent): - pass - - -class VerifyAgent1627(VerifyAgent1597): - pass - - -class VerificationItem813(VerificationItem): - pass - - -class DeclaredBy820(DeclaredBy): - pass - - -class VerifyAgent1628(VerifyAgent): - pass - - -class VerifyAgent1629(VerifyAgent1597): - pass - - -class VerificationItem814(VerificationItem): - pass - - -class DeclaredBy821(DeclaredBy): - pass - - -class VerifyAgent1630(VerifyAgent): - pass - - -class VerifyAgent1631(VerifyAgent1597): - pass - - -class VerificationItem815(VerificationItem): - pass - - -class DeclaredBy822(DeclaredBy): - pass - - -class VerifyAgent1632(VerifyAgent): - pass - - -class VerifyAgent1633(VerifyAgent1597): - pass - - -class VerificationItem816(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy823(DeclaredBy): - pass - - -class VerifyAgent1634(VerifyAgent): - pass - - -class VerifyAgent1635(VerifyAgent1597): - pass - - -class VerificationItem817(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy824(DeclaredBy): - pass - - -class VerifyAgent1636(VerifyAgent): - pass - - -class VerifyAgent1637(VerifyAgent1597): - pass - - -class VerificationItem818(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy825(DeclaredBy): - pass - - -class VerifyAgent1638(VerifyAgent): - pass - - -class VerifyAgent1639(VerifyAgent1597): - pass - - -class VerificationItem819(VerificationItem): - pass - - -class Target83(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy826(DeclaredBy): - pass - - -class VerifyAgent1640(VerifyAgent): - pass - - -class VerifyAgent1641(VerifyAgent1597): - pass - - -class VerificationItem820(VerificationItem): - pass - - -class DeclaredBy827(DeclaredBy): - pass - - -class VerifyAgent1642(VerifyAgent): - pass - - -class VerifyAgent1643(VerifyAgent1597): - pass - - -class VerificationItem821(VerificationItem): - pass - - -class DeclaredBy828(DeclaredBy): - pass - - -class VerifyAgent1644(VerifyAgent): - pass - - -class VerifyAgent1645(VerifyAgent1597): - pass - - -class VerificationItem822(VerificationItem): - pass - - -class DeclaredBy829(DeclaredBy): - pass - - -class VerifyAgent1646(VerifyAgent): - pass - - -class VerifyAgent1647(VerifyAgent1597): - pass - - -class VerificationItem823(VerificationItem): - pass - - -class DeclaredBy830(DeclaredBy): - pass - - -class VerifyAgent1648(VerifyAgent): - pass - - -class VerifyAgent1649(VerifyAgent1597): - pass - - -class VerificationItem824(VerificationItem): - pass - - -class DeclaredBy831(DeclaredBy): - pass - - -class VerifyAgent1650(VerifyAgent): - pass - - -class VerifyAgent1651(VerifyAgent1597): - pass - - -class VerificationItem825(VerificationItem): - pass - - -class DeclaredBy832(DeclaredBy): - pass - - -class VerifyAgent1652(VerifyAgent): - pass - - -class VerifyAgent1653(VerifyAgent1597): - pass - - -class VerificationItem826(VerificationItem): - pass - - -class DeclaredBy833(DeclaredBy): - pass - - -class VerifyAgent1654(VerifyAgent): - pass - - -class VerifyAgent1655(VerifyAgent1597): - pass - - -class VerificationItem827(VerificationItem): - pass - - -class DeclaredBy834(DeclaredBy): - pass - - -class VerifyAgent1656(VerifyAgent): - pass - - -class VerifyAgent1657(VerifyAgent1597): - pass - - -class VerificationItem828(VerificationItem): - pass - - -class DeclaredBy835(DeclaredBy): - pass - - -class VerifyAgent1658(VerifyAgent): - pass - - -class VerifyAgent1659(VerifyAgent1597): - pass - - -class VerificationItem829(VerificationItem): - pass - - -class DeclaredBy836(DeclaredBy): - pass - - -class VerifyAgent1660(VerifyAgent): - pass - - -class VerifyAgent1661(VerifyAgent1597): - pass - - -class VerificationItem830(VerificationItem): - pass - - -class DeclaredBy837(DeclaredBy): - pass - - -class VerifyAgent1662(VerifyAgent): - pass - - -class VerifyAgent1663(VerifyAgent1597): - pass - - -class VerificationItem831(VerificationItem): - pass - - -class DeclaredBy838(DeclaredBy): - pass - - -class VerifyAgent1664(VerifyAgent): - pass - - -class VerifyAgent1665(VerifyAgent1597): - pass - - -class VerificationItem832(VerificationItem): - pass - - -class DeclaredBy839(DeclaredBy): - pass - - -class VerifyAgent1666(VerifyAgent): - pass - - -class VerifyAgent1667(VerifyAgent1597): - pass - - -class VerificationItem833(VerificationItem): - pass - - -class DeclaredBy840(DeclaredBy): - pass - - -class VerifyAgent1668(VerifyAgent): - pass - - -class VerifyAgent1669(VerifyAgent1597): - pass - - -class VerificationItem834(VerificationItem): - pass - - -class ReferenceAsset32(ReferenceAsset): - pass - - -class FeedFieldMapping33(FeedFieldMapping): - pass - - -class ReferenceAuthorization32(ReferenceAuthorization): - pass - - -class DeclaredBy841(DeclaredBy): - pass - - -class VerifyAgent1670(VerifyAgent): - pass - - -class VerifyAgent1671(VerifyAgent1597): - pass - - -class VerificationItem835(VerificationItem): - pass - - -class DeclaredBy842(DeclaredBy): - pass - - -class VerifyAgent1672(VerifyAgent): - pass - - -class VerifyAgent1673(VerifyAgent1597): - pass - - -class VerificationItem836(VerificationItem): - pass - - -class DeclaredBy843(DeclaredBy): - pass - - -class VerifyAgent1674(VerifyAgent): - pass - - -class VerifyAgent1675(VerifyAgent1597): - pass - - -class VerificationItem837(VerificationItem): - pass - - -class DeclaredBy844(DeclaredBy): - pass - - -class VerifyAgent1676(VerifyAgent): - pass - - -class VerifyAgent1677(VerifyAgent1597): - pass - - -class VerificationItem838(VerificationItem): - pass - - -class DeclaredBy845(DeclaredBy): - pass - - -class VerifyAgent1678(VerifyAgent): - pass - - -class VerifyAgent1679(VerifyAgent1597): - pass - - -class VerificationItem839(VerificationItem): - pass - - -class DeclaredBy846(DeclaredBy): - pass - - -class VerifyAgent1680(VerifyAgent): - pass - - -class VerifyAgent1681(VerifyAgent1597): - pass - - -class VerificationItem840(VerificationItem): - pass - - -class DeclaredBy847(DeclaredBy): - pass - - -class VerifyAgent1682(VerifyAgent): - pass - - -class VerifyAgent1683(VerifyAgent1597): - pass - - -class VerificationItem841(VerificationItem): - pass - - -class DeclaredBy848(DeclaredBy): - pass - - -class VerifyAgent1684(VerifyAgent): - pass - - -class VerifyAgent1685(VerifyAgent1597): - pass - - -class VerificationItem842(VerificationItem): - pass - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to apply for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class Status208(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class Type(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy849(DeclaredBy): - pass - - -class VerifyAgent1686(VerifyAgent): - pass - - -class VerifyAgent1687(VerifyAgent1597): - pass - - -class VerificationItem843(VerificationItem): - pass - - - -class DeclaredBy850(DeclaredBy): - pass - - -class VerifyAgent1688(VerifyAgent): - pass - - -class VerifyAgent1689(VerifyAgent1597): - pass - - -class VerificationItem844(VerificationItem): - pass - - -class DeclaredBy851(DeclaredBy): - pass - - -class VerifyAgent1690(VerifyAgent): - pass - - -class VerifyAgent1691(VerifyAgent1597): - pass - - -class VerificationItem845(VerificationItem): - pass - - -class DeclaredBy852(DeclaredBy): - pass - - -class VerifyAgent1692(VerifyAgent): - pass - - -class VerifyAgent1693(VerifyAgent1597): - pass - - -class VerificationItem846(VerificationItem): - pass - - -class DeclaredBy853(DeclaredBy): - pass - - -class VerifyAgent1694(VerifyAgent): - pass - - -class VerifyAgent1695(VerifyAgent1597): - pass - - -class VerificationItem847(VerificationItem): - pass - - -class DeclaredBy854(DeclaredBy): - pass - - -class VerifyAgent1696(VerifyAgent): - pass - - -class VerifyAgent1697(VerifyAgent1597): - pass - - -class VerificationItem848(VerificationItem): - pass - - -class DeclaredBy855(DeclaredBy): - pass - - -class VerifyAgent1698(VerifyAgent): - pass - - -class VerifyAgent1699(VerifyAgent1597): - pass - - -class VerificationItem849(VerificationItem): - pass - - -class DeclaredBy856(DeclaredBy): - pass - - -class VerifyAgent1700(VerifyAgent): - pass - - -class VerifyAgent1701(VerifyAgent1597): - pass - - -class VerificationItem850(VerificationItem): - pass - - -class DeclaredBy857(DeclaredBy): - pass - - -class VerifyAgent1702(VerifyAgent): - pass - - -class VerifyAgent1703(VerifyAgent1597): - pass - - -class VerificationItem851(VerificationItem): - pass - - -class DeclaredBy858(DeclaredBy): - pass - - -class VerifyAgent1704(VerifyAgent): - pass - - -class VerifyAgent1705(VerifyAgent1597): - pass - - -class VerificationItem852(VerificationItem): - pass - - -class DeclaredBy859(DeclaredBy): - pass - - -class VerifyAgent1706(VerifyAgent): - pass - - -class VerifyAgent1707(VerifyAgent1597): - pass - - -class VerificationItem853(VerificationItem): - pass - - -class DeclaredBy860(DeclaredBy): - pass - - -class VerifyAgent1708(VerifyAgent): - pass - - -class VerifyAgent1709(VerifyAgent1597): - pass - - -class VerificationItem854(VerificationItem): - pass - - -class DeclaredBy861(DeclaredBy): - pass - - -class VerifyAgent1710(VerifyAgent): - pass - - -class VerifyAgent1711(VerifyAgent1597): - pass - - -class VerificationItem855(VerificationItem): - pass - - -class DeclaredBy862(DeclaredBy): - pass - - -class VerifyAgent1712(VerifyAgent): - pass - - -class VerifyAgent1713(VerifyAgent1597): - pass - - -class VerificationItem856(VerificationItem): - pass - - -class DeclaredBy863(DeclaredBy): - pass - - -class VerifyAgent1714(VerifyAgent): - pass - - -class VerifyAgent1715(VerifyAgent1597): - pass - - -class VerificationItem857(VerificationItem): - pass - - -class ReferenceAsset33(ReferenceAsset): - pass - - -class FeedFieldMapping34(FeedFieldMapping): - pass - - -class ReferenceAuthorization33(ReferenceAuthorization): - pass - - -class DeclaredBy864(DeclaredBy): - pass - - -class VerifyAgent1716(VerifyAgent): - pass - - -class VerifyAgent1717(VerifyAgent1597): - pass - - -class VerificationItem858(VerificationItem): - pass - - -class DeclaredBy865(DeclaredBy): - pass - - -class VerifyAgent1718(VerifyAgent): - pass - - -class VerifyAgent1719(VerifyAgent1597): - pass - - -class VerificationItem859(VerificationItem): - pass - - -class DeclaredBy866(DeclaredBy): - pass - - -class VerifyAgent1720(VerifyAgent): - pass - - -class VerifyAgent1721(VerifyAgent1597): - pass - - -class VerificationItem860(VerificationItem): - pass - - -class DeclaredBy867(DeclaredBy): - pass - - -class VerifyAgent1722(VerifyAgent): - pass - - -class VerifyAgent1723(VerifyAgent1597): - pass - - -class VerificationItem861(VerificationItem): - pass - - -class DeclaredBy868(DeclaredBy): - pass - - -class VerifyAgent1724(VerifyAgent): - pass - - -class VerifyAgent1725(VerifyAgent1597): - pass - - -class VerificationItem862(VerificationItem): - pass - - -class DeclaredBy869(DeclaredBy): - pass - - -class VerifyAgent1726(VerifyAgent): - pass - - -class VerifyAgent1727(VerifyAgent1597): - pass - - -class VerificationItem863(VerificationItem): - pass - - -class DeclaredBy870(DeclaredBy): - pass - - -class VerifyAgent1728(VerifyAgent): - pass - - -class VerifyAgent1729(VerifyAgent1597): - pass - - -class VerificationItem864(VerificationItem): - pass - - -class DeclaredBy871(DeclaredBy): - pass - - -class VerifyAgent1730(VerifyAgent): - pass - - -class VerifyAgent1731(VerifyAgent1597): - pass - - -class VerificationItem865(VerificationItem): - pass - - -class DeclaredBy872(DeclaredBy): - pass - - -class VerifyAgent1732(VerifyAgent): - pass - - -class VerifyAgent1733(VerifyAgent1597): - pass - - -class VerificationItem866(VerificationItem): - pass - - -class DeclaredBy873(DeclaredBy): - pass - - -class VerifyAgent1734(VerifyAgent): - pass - - -class VerifyAgent1735(VerifyAgent1597): - pass - - -class VerificationItem867(VerificationItem): - pass - - -class DeclaredBy874(DeclaredBy): - pass - - -class VerifyAgent1736(VerifyAgent): - pass - - -class VerifyAgent1737(VerifyAgent1597): - pass - - -class VerificationItem868(VerificationItem): - pass - - -class DeclaredBy875(DeclaredBy): - pass - - -class VerifyAgent1738(VerifyAgent): - pass - - -class VerifyAgent1739(VerifyAgent1597): - pass - - -class VerificationItem869(VerificationItem): - pass - - -class DeclaredBy876(DeclaredBy): - pass - - -class VerifyAgent1740(VerifyAgent): - pass - - -class VerifyAgent1741(VerifyAgent1597): - pass - - -class VerificationItem870(VerificationItem): - pass - - -class DeclaredBy877(DeclaredBy): - pass - - -class VerifyAgent1742(VerifyAgent): - pass - - -class VerifyAgent1743(VerifyAgent1597): - pass - - -class VerificationItem871(VerificationItem): - pass - - -class DeclaredBy878(DeclaredBy): - pass - - -class VerifyAgent1744(VerifyAgent): - pass - - -class VerifyAgent1745(VerifyAgent1597): - pass - - -class VerificationItem872(VerificationItem): - pass - - -class DeclaredBy879(DeclaredBy): - pass - - -class VerifyAgent1746(VerifyAgent): - pass - - -class VerifyAgent1747(VerifyAgent1597): - pass - - -class VerificationItem873(VerificationItem): - pass - - -class DeclaredBy880(DeclaredBy): - pass - - -class VerifyAgent1748(VerifyAgent): - pass - - -class VerifyAgent1749(VerifyAgent1597): - pass - - -class VerificationItem874(VerificationItem): - pass - - -class DeclaredBy881(DeclaredBy): - pass - - -class VerifyAgent1750(VerifyAgent): - pass - - -class VerifyAgent1751(VerifyAgent1597): - pass - - -class VerificationItem875(VerificationItem): - pass - - -class DeclaredBy882(DeclaredBy): - pass - - -class VerifyAgent1752(VerifyAgent): - pass - - -class VerifyAgent1753(VerifyAgent1597): - pass - - -class VerificationItem876(VerificationItem): - pass - - -class DeclaredBy883(DeclaredBy): - pass - - -class VerifyAgent1754(VerifyAgent): - pass - - -class VerifyAgent1755(VerifyAgent1597): - pass - - -class VerificationItem877(VerificationItem): - pass - - -class DeclaredBy884(DeclaredBy): - pass - - -class VerifyAgent1756(VerifyAgent): - pass - - -class VerifyAgent1757(VerifyAgent1597): - pass - - -class VerificationItem878(VerificationItem): - pass - - -class DeclaredBy885(DeclaredBy): - pass - - -class VerifyAgent1758(VerifyAgent): - pass - - -class VerifyAgent1759(VerifyAgent1597): - pass - - -class VerificationItem879(VerificationItem): - pass - - -class ReferenceAsset34(ReferenceAsset): - pass - - -class FeedFieldMapping35(FeedFieldMapping): - pass - - -class ReferenceAuthorization34(ReferenceAuthorization): - pass - - -class DeclaredBy886(DeclaredBy): - pass - - -class VerifyAgent1760(VerifyAgent): - pass - - -class VerifyAgent1761(VerifyAgent1597): - pass - - -class VerificationItem880(VerificationItem): - pass - - -class DeclaredBy887(DeclaredBy): - pass - - -class VerifyAgent1762(VerifyAgent): - pass - - -class VerifyAgent1763(VerifyAgent1597): - pass - - -class VerificationItem881(VerificationItem): - pass - - -class DeclaredBy888(DeclaredBy): - pass - - -class VerifyAgent1764(VerifyAgent): - pass - - -class VerifyAgent1765(VerifyAgent1597): - pass - - -class VerificationItem882(VerificationItem): - pass - - -class DeclaredBy889(DeclaredBy): - pass - - -class VerifyAgent1766(VerifyAgent): - pass - - -class VerifyAgent1767(VerifyAgent1597): - pass - - -class VerificationItem883(VerificationItem): - pass - - -class DeclaredBy890(DeclaredBy): - pass - - -class VerifyAgent1768(VerifyAgent): - pass - - -class VerifyAgent1769(VerifyAgent1597): - pass - - -class VerificationItem884(VerificationItem): - pass - - -class DeclaredBy891(DeclaredBy): - pass - - -class VerifyAgent1770(VerifyAgent): - pass - - -class VerifyAgent1771(VerifyAgent1597): - pass - - -class VerificationItem885(VerificationItem): - pass - - -class DeclaredBy892(DeclaredBy): - pass - - -class VerifyAgent1772(VerifyAgent): - pass - - -class VerifyAgent1773(VerifyAgent1597): - pass - - -class VerificationItem886(VerificationItem): - pass - - -class DeclaredBy893(DeclaredBy): - pass - - -class VerifyAgent1774(VerifyAgent): - pass - - -class VerifyAgent1775(VerifyAgent1597): - pass - - -class VerificationItem887(VerificationItem): - pass - - -class IndustryIdentifier16(IndustryIdentifier): - pass - - -class DeclaredBy894(DeclaredBy): - pass - - -class VerifyAgent1776(VerifyAgent): - pass - - -class VerifyAgent1777(VerifyAgent1597): - pass - - -class VerificationItem888(VerificationItem): - pass - - -class Assignment(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - creative_id: Annotated[str, Field(description='ID of the creative to assign')] - package_id: Annotated[str, Field(description='ID of the package to assign the creative to')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight (0-100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Restrict this creative to specific placements within the package. When omitted, the creative is eligible for all placements.', - min_length=1, - ), - ] = None - - -class ValidationMode(StrEnum): - strict = 'strict' - lenient = 'lenient' - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1597 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction838] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account48(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class EmbeddedProvenanceItem799(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1598 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark799(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1599 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction839(Jurisdiction838): - pass - - -class Disclosure801(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction839] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance798(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy805 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem799] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark799] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure801 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem799] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance798 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem800(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1600 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark800(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1601 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction840(Jurisdiction838): - pass - - -class Disclosure802(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction840] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance799(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy806 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem800] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark800] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure802 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem800] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets431(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance799 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem801(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1602 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark801(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1603 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction841(Jurisdiction838): - pass - - -class Disclosure803(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction841] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance800(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy807 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem801] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark801] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure803 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem801] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets432(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance800 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem802(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1604 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark802(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1605 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction842(Jurisdiction838): - pass - - -class Disclosure804(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction842] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance801(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy808 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem802] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark802] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure804 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem802] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets433(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance801 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem803(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1606 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark803(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1607 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction843(Jurisdiction838): - pass - - -class Disclosure805(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction843] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance802(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy809 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem803] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark803] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure805 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem803] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets434(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance802 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem804(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1608 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark804(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1609 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction844(Jurisdiction838): - pass - - -class Disclosure806(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction844] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance803(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy810 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem804] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark804] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure806 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem804] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets435(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance803 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem805(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1610 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark805(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1611 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction845(Jurisdiction838): - pass - - -class Disclosure807(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction845] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance804(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy811 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem805] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark805] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure807 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem805] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets436(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance804 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem806(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1612 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark806(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1613 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction846(Jurisdiction838): - pass - - -class Disclosure808(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction846] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance805(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy812 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem806] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark806] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure808 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem806] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets437(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance805 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem807(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1614 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark807(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1615 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction847(Jurisdiction838): - pass - - -class Disclosure809(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction847] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance806(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy813 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem807] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark807] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure809 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem807] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets438(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance806 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem808(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1616 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark808(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1617 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction848(Jurisdiction838): - pass - - -class Disclosure810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction848] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance807(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy814 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem808] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark808] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure810 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem808] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets439(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance807 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem809(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1618 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark809(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1619 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction849(Jurisdiction838): - pass - - -class Disclosure811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction849] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance808(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy815 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem809] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark809] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure811 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem809] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets440(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance808 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1620 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1621 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction850(Jurisdiction838): - pass - - -class Disclosure812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction850] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance809(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy816 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem810] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark810] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure812 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem810] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets441(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance809 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1622 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1623 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction851(Jurisdiction838): - pass - - -class Disclosure813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction851] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy817 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem811] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark811] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure813 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem811] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance810 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1624 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1625 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction852(Jurisdiction838): - pass - - -class Disclosure814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction852] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy818 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem812] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark812] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure814 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem812] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance811 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets445(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets446(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1626 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1627 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction854(Jurisdiction838): - pass - - -class Disclosure815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction854] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy819 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem813] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark813] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure815 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem813] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance812 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1628 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1629 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction855(Jurisdiction838): - pass - - -class Disclosure816(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction855] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy820 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem814] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark814] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure816 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem814] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance813 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1630 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1631 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction856(Jurisdiction838): - pass - - -class Disclosure817(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction856] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy821 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem815] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark815] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure817 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem815] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance814 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem816(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1632 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark816(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1633 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction857(Jurisdiction838): - pass - - -class Disclosure818(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction857] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy822 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem816] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark816] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure818 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem816] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance815 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem817(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1634 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark817(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1635 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction858(Jurisdiction838): - pass - - -class Disclosure819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction858] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance816(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy823 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem817] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark817] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure819 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem817] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media61, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance816 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem818(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1636 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark818(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1637 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction859(Jurisdiction838): - pass - - -class Disclosure820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction859] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance817(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy824 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem818] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark818] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure820 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem818] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance817 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1638 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1639 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction860(Jurisdiction838): - pass - - -class Disclosure821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction860] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance818(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy825 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem819] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark819] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure821 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem819] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets450(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance818 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1640 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1641 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction861(Jurisdiction838): - pass - - -class Disclosure822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction861] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy826 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem820] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark820] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure822 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem820] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets451(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target83 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target83.linear - provenance: Annotated[ - Provenance819 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1642 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1643 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction862(Jurisdiction838): - pass - - -class Disclosure823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction862] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy827 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem821] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark821] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure823 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem821] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance820 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1644 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1645 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction863(Jurisdiction838): - pass - - -class Disclosure824(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction863] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy828 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem822] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark822] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure824 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem822] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance821 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1646 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1647 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction864(Jurisdiction838): - pass - - -class Disclosure825(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction864] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy829 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem823] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark823] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure825 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem823] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance822 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem824(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1648 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark824(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1649 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction865(Jurisdiction838): - pass - - -class Disclosure826(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction865] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy830 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem824] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark824] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure826 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem824] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance823 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem825(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1650 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark825(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1651 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction866(Jurisdiction838): - pass - - -class Disclosure827(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction866] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance824(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy831 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem825] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark825] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure827 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem825] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance824 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem826(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1652 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark826(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1653 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction867(Jurisdiction838): - pass - - -class Disclosure828(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction867] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance825(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy832 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem826] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark826] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure828 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem826] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance825 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem827(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1654 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark827(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1655 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction868(Jurisdiction838): - pass - - -class Disclosure829(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction868] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance826(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy833 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem827] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark827] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure829 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem827] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance826 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem828(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1656 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark828(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1657 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction869(Jurisdiction838): - pass - - -class Disclosure830(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction869] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance827(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy834 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem828] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark828] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure830 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem828] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance827 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem829(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1658 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark829(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1659 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction870(Jurisdiction838): - pass - - -class Disclosure831(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction870] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance828(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy835 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem829] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark829] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure831 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem829] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance828 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem830(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1660 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark830(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1661 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction871(Jurisdiction838): - pass - - -class Disclosure832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction871] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance829(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy836 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem830] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark830] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure832 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem830] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance829 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem831(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1662 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark831(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1663 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction872(Jurisdiction838): - pass - - -class Disclosure833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction872] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance830(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy837 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem831] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark831] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure833 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem831] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance830 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1664 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1665 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction873(Jurisdiction838): - pass - - -class Disclosure834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction873] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance831(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy838 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem832] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark832] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure834 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem832] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance831 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1666 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1667 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction874(Jurisdiction838): - pass - - -class Disclosure835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction874] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy839 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem833] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark833] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure835 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem833] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance832 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1668 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1669 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction875(Jurisdiction838): - pass - - -class Disclosure836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction875] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy840 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem834] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark834] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure836 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem834] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance833 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure32(RequiredDisclosure): - pass - - -class Compliance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure32] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets45217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset32] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance32 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets45218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping33] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1670 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1671 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction877(Jurisdiction838): - pass - - -class Disclosure837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction877] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy841 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem835] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark835] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure837 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem835] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization32 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance834 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1672 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1673 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction878(Jurisdiction838): - pass - - -class Disclosure838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction878] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy842 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem836] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark836] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure838 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem836] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance835 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1674 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1675 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction879(Jurisdiction838): - pass - - -class Disclosure839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction879] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy843 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem837] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark837] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure839 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem837] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance836 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1676 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1677 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction880(Jurisdiction838): - pass - - -class Disclosure840(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction880] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy844 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem838] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark838] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure840 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem838] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance837 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1678 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1679 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction881(Jurisdiction838): - pass - - -class Disclosure841(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction881] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy845 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem839] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark839] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure841 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem839] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media62 | Media63, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl31 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance838 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem840(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1680 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark840(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1681 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction882(Jurisdiction838): - pass - - -class Disclosure842(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction882] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy846 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem840] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark840] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure842 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem840] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance839 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem841(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1682 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark841(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1683 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction883(Jurisdiction838): - pass - - -class Disclosure843(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction883] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance840(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy847 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem841] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark841] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure843 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem841] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance840 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem842(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1684 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark842(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1685 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction884(Jurisdiction838): - pass - - -class Disclosure844(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction884] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance841(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy848 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem842] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark842] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure844 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem842] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets45223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target83 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target83.linear - provenance: Annotated[ - Provenance841 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets4521( - RootModel[ - Assets4522 - | Assets4523 - | Assets4524 - | Assets4525 - | Assets4526 - | Assets4527 - | Assets4528 - | Assets4529 - | Assets45210 - | Assets45211 - | Assets45212 - | Assets45213 - | Assets45214 - | Assets45215 - | Assets444 - | Assets45217 - | Assets45218 - | Assets45219 - | Assets45220 - | Assets45221 - | Assets45222 - | Assets45223 - ] -): - root: Annotated[ - Assets4522 - | Assets4523 - | Assets4524 - | Assets4525 - | Assets4526 - | Assets4527 - | Assets4528 - | Assets4529 - | Assets45210 - | Assets45211 - | Assets45212 - | Assets45213 - | Assets45214 - | Assets45215 - | Assets444 - | Assets45217 - | Assets45218 - | Assets45219 - | Assets45220 - | Assets45221 - | Assets45222 - | Assets45223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets452(RootModel[list[Assets4521]]): - root: Annotated[list[Assets4521], Field(min_length=1)] - - -class EmbeddedProvenanceItem843(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1686 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark843(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1687 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction885(Jurisdiction838): - pass - - -class Disclosure845(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction885] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance842(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy849 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem843] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark843] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure845 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem843] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] - format_kind: Annotated[ - FormatKind | None, - Field( - description='3.1+ canonical-format path. The canonical format name this creative targets (e.g., `image`, `video_hosted`). Mutually exclusive with `format_id`.', - title='Canonical Format Kind', - ), - ] = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef29 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets431 - | Assets432 - | Assets433 - | Assets434 - | Assets435 - | Assets436 - | Assets437 - | Assets438 - | Assets439 - | Assets440 - | Assets441 - | Assets442 - | Assets443 - | Assets444 - | Assets445 - | Assets446 - | Assets447 - | Assets448 - | Assets449 - | Assets450 - | Assets451 - | Assets452, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status208 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance842 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem844(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1688 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark844(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1689 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction886(Jurisdiction838): - pass - - -class Disclosure846(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction886] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance843(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy850 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem844] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark844] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure846 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem844] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance843 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem845(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1690 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark845(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1691 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction887(Jurisdiction838): - pass - - -class Disclosure847(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction887] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance844(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy851 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem845] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark845] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure847 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem845] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance844 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem846(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1692 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark846(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1693 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction888(Jurisdiction838): - pass - - -class Disclosure848(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction888] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance845(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy852 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem846] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark846] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure848 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem846] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance845 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem847(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1694 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark847(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1695 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction889(Jurisdiction838): - pass - - -class Disclosure849(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction889] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance846(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy853 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem847] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark847] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure849 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem847] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance846 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem848(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1696 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark848(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1697 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction890(Jurisdiction838): - pass - - -class Disclosure850(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction890] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance847(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy854 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem848] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark848] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure850 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem848] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance847 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem849(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1698 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark849(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1699 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction891(Jurisdiction838): - pass - - -class Disclosure851(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction891] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance848(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy855 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem849] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark849] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure851 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem849] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance848 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem850(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1700 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark850(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1701 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction892(Jurisdiction838): - pass - - -class Disclosure852(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction892] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance849(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy856 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem850] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark850] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure852 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem850] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance849 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem851(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1702 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark851(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1703 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction893(Jurisdiction838): - pass - - -class Disclosure853(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction893] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance850(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy857 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem851] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark851] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure853 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem851] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets460(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance850 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem852(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1704 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark852(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1705 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction894(Jurisdiction838): - pass - - -class Disclosure854(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction894] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance851(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy858 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem852] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark852] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure854 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem852] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets461(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance851 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem853(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1706 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark853(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1707 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction895(Jurisdiction838): - pass - - -class Disclosure855(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction895] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance852(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy859 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem853] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark853] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure855 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem853] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets462(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance852 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem854(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1708 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark854(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1709 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction896(Jurisdiction838): - pass - - -class Disclosure856(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction896] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance853(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy860 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem854] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark854] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure856 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem854] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets463(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance853 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem855(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1710 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark855(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1711 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction897(Jurisdiction838): - pass - - -class Disclosure857(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction897] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance854(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy861 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem855] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark855] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure857 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem855] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets464(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance854 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem856(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1712 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark856(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1713 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction898(Jurisdiction838): - pass - - -class Disclosure858(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction898] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance855(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy862 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem856] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark856] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure858 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem856] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets465(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance855 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem857(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1714 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark857(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1715 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction899(Jurisdiction838): - pass - - -class Disclosure859(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction899] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance856(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy863 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem857] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark857] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure859 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem857] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets466(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance856 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets467(Assets444): - pass - - -class RequiredDisclosure33(RequiredDisclosure): - pass - - -class Compliance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure33] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets468(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset33] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance33 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets469(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping34] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem858(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1716 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark858(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1717 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction901(Jurisdiction838): - pass - - -class Disclosure860(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction901] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance857(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy864 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem858] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark858] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure860 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem858] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets470(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization33 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance857 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem859(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1718 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark859(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1719 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction902(Jurisdiction838): - pass - - -class Disclosure861(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction902] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance858(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy865 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem859] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark859] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure861 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem859] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance858 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem860(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1720 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark860(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1721 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction903(Jurisdiction838): - pass - - -class Disclosure862(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction903] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance859(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy866 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem860] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark860] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure862 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem860] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance859 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem861(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1722 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark861(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1723 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction904(Jurisdiction838): - pass - - -class Disclosure863(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction904] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance860(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy867 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem861] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark861] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure863 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem861] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance860 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem862(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1724 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark862(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1725 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction905(Jurisdiction838): - pass - - -class Disclosure864(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction905] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance861(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy868 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem862] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark862] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure864 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem862] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets471(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media64 | Media65, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl32 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance861 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem863(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1726 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark863(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1727 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction906(Jurisdiction838): - pass - - -class Disclosure865(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction906] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance862(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy869 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem863] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark863] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure865 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem863] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets472(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance862 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem864(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1728 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark864(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1729 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction907(Jurisdiction838): - pass - - -class Disclosure866(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction907] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance863(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy870 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem864] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark864] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure866 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem864] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets473(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance863 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem865(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1730 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark865(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1731 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction908(Jurisdiction838): - pass - - -class Disclosure867(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction908] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance864(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy871 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem865] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark865] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure867 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem865] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets474(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target83 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target83.linear - provenance: Annotated[ - Provenance864 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem866(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1732 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark866(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1733 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction909(Jurisdiction838): - pass - - -class Disclosure868(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction909] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance865(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy872 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem866] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark866] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure868 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem866] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4752(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance865 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem867(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1734 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark867(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1735 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction910(Jurisdiction838): - pass - - -class Disclosure869(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction910] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance866(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy873 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem867] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark867] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure869 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem867] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4753(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance866 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem868(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1736 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark868(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1737 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction911(Jurisdiction838): - pass - - -class Disclosure870(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction911] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance867(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy874 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem868] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark868] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure870 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem868] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4754(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance867 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem869(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1738 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark869(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1739 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction912(Jurisdiction838): - pass - - -class Disclosure871(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction912] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance868(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy875 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem869] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark869] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure871 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem869] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4755(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance868 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem870(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1740 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark870(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1741 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction913(Jurisdiction838): - pass - - -class Disclosure872(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction913] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance869(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy876 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem870] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark870] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure872 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem870] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4756(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance869 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem871(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1742 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark871(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1743 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction914(Jurisdiction838): - pass - - -class Disclosure873(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction914] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance870(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy877 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem871] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark871] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure873 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem871] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4757(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance870 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem872(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1744 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark872(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1745 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction915(Jurisdiction838): - pass - - -class Disclosure874(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction915] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance871(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy878 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem872] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark872] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure874 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem872] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4758(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance871 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem873(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1746 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark873(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1747 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction916(Jurisdiction838): - pass - - -class Disclosure875(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction916] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance872(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy879 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem873] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark873] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure875 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem873] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4759(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance872 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem874(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1748 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark874(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1749 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction917(Jurisdiction838): - pass - - -class Disclosure876(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction917] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance873(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy880 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem874] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark874] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure876 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem874] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance873 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem875(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1750 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark875(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1751 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction918(Jurisdiction838): - pass - - -class Disclosure877(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction918] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance874(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy881 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem875] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark875] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure877 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem875] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance874 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem876(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1752 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark876(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1753 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction919(Jurisdiction838): - pass - - -class Disclosure878(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction919] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance875(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy882 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem876] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark876] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure878 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem876] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance875 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem877(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1754 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark877(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1755 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction920(Jurisdiction838): - pass - - -class Disclosure879(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction920] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance876(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy883 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem877] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark877] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure879 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem877] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance876 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem878(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1756 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark878(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1757 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction921(Jurisdiction838): - pass - - -class Disclosure880(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction921] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance877(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy884 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem878] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark878] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure880 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem878] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance877 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem879(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1758 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark879(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1759 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction922(Jurisdiction838): - pass - - -class Disclosure881(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction922] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance878(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy885 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem879] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark879] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure881 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem879] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance878 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure34(RequiredDisclosure): - pass - - -class Compliance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure34] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets47517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset34] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance34 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets47518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping35] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem880(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1760 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark880(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1761 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction924(Jurisdiction838): - pass - - -class Disclosure882(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction924] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance879(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy886 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem880] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark880] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure882 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem880] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization34 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance879 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem881(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1762 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark881(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1763 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction925(Jurisdiction838): - pass - - -class Disclosure883(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction925] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance880(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy887 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem881] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark881] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure883 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem881] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance880 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem882(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1764 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark882(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1765 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction926(Jurisdiction838): - pass - - -class Disclosure884(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction926] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance881(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy888 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem882] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark882] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure884 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem882] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance881 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem883(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1766 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark883(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1767 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction927(Jurisdiction838): - pass - - -class Disclosure885(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction927] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance882(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy889 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem883] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark883] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure885 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem883] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance882 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem884(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1768 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark884(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1769 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction928(Jurisdiction838): - pass - - -class Disclosure886(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction928] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance883(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy890 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem884] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark884] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure886 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem884] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media66 | Media67, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl33 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance883 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem885(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1770 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark885(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1771 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction929(Jurisdiction838): - pass - - -class Disclosure887(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction929] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance884(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy891 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem885] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark885] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure887 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem885] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance884 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem886(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1772 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark886(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1773 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction930(Jurisdiction838): - pass - - -class Disclosure888(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction930] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance885(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy892 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem886] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark886] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure888 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem886] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance885 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem887(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1774 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark887(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1775 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction931(Jurisdiction838): - pass - - -class Disclosure889(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction931] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance886(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy893 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem887] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark887] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure889 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem887] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target83 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target83.linear - provenance: Annotated[ - Provenance886 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets4751( - RootModel[ - Assets4752 - | Assets4753 - | Assets4754 - | Assets4755 - | Assets4756 - | Assets4757 - | Assets4758 - | Assets4759 - | Assets47510 - | Assets47511 - | Assets47512 - | Assets47513 - | Assets47514 - | Assets47515 - | Assets444 - | Assets47517 - | Assets47518 - | Assets47519 - | Assets47520 - | Assets47521 - | Assets47522 - | Assets47523 - ] -): - root: Annotated[ - Assets4752 - | Assets4753 - | Assets4754 - | Assets4755 - | Assets4756 - | Assets4757 - | Assets4758 - | Assets4759 - | Assets47510 - | Assets47511 - | Assets47512 - | Assets47513 - | Assets47514 - | Assets47515 - | Assets444 - | Assets47517 - | Assets47518 - | Assets47519 - | Assets47520 - | Assets47521 - | Assets47522 - | Assets47523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets475(RootModel[list[Assets4751]]): - root: Annotated[list[Assets4751], Field(min_length=1)] - - -class EmbeddedProvenanceItem888(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1776 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark888(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1777 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction932(Jurisdiction838): - pass - - -class Disclosure890(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction932] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance887(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy894 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem888] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark888] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure890 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem888] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId | None, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: Annotated[ - FormatKind, - Field( - description='3.1+ canonical-format path. The canonical format name this creative targets (e.g., `image`, `video_hosted`). Mutually exclusive with `format_id`.', - title='Canonical Format Kind', - ), - ] - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef29 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets460 - | Assets461 - | Assets462 - | Assets463 - | Assets464 - | Assets465 - | Assets466 - | Assets467 - | Assets468 - | Assets469 - | Assets470 - | Assets471 - | Assets472 - | Assets473 - | Assets474 - | Assets475, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status208 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier16] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance887 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class SyncCreativesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account48, - Field( - description='Account that owns these creatives.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - creatives: Annotated[ - list[Creatives | Creatives1], - Field( - description='Array of creative assets to sync (create or update)', - max_length=100, - min_length=1, - ), - ] - creative_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to limit sync scope to specific creative IDs. When provided, only these creatives will be created/updated. Other creatives in the library are unaffected. Useful for partial updates and error recovery.', - max_length=100, - min_length=1, - ), - ] = None - assignments: Annotated[ - list[Assignment] | None, - Field( - description='Optional bulk assignment of creatives to packages. Each entry maps one creative to one package with optional weight and placement targeting. Standalone creative agents that do not manage media buys ignore this field.', - min_length=1, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated idempotency key for safe retries. If a sync fails without a response, resending with the same idempotency_key guarantees at-most-once execution. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - delete_missing: Annotated[ - bool | None, - Field( - description='When true, creatives not included in this sync will be archived. Use with caution for full library replacement. Invalid when creative_ids is provided — delete_missing applies to the entire library scope, not a filtered subset.' - ), - ] = False - dry_run: Annotated[ - bool | None, - Field( - description='When true, preview changes without applying them. Returns what would be created/updated/deleted.' - ), - ] = False - validation_mode: Annotated[ - ValidationMode | None, - Field( - description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid creatives and reports errors.", - title='Validation Mode', - ), - ] = ValidationMode.strict - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async sync notifications. The agent will send a webhook when sync completes if the operation takes longer than immediate response time (typically for large bulk operations or manual approval/HITL).', - title='Push Notification Config', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/sync_creatives_response.py b/src/adcp/types/generated_poc/bundled/creative/sync_creatives_response.py deleted file mode 100644 index 06ce59f7..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/sync_creatives_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/sync_creatives_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SyncCreativesResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/validate_input_request.py b/src/adcp/types/generated_poc/bundled/creative/validate_input_request.py deleted file mode 100644 index c16d031b..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/validate_input_request.py +++ /dev/null @@ -1,21563 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/validate_input_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent2493(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class DeclaredBy1253(DeclaredBy): - pass - - -class VerifyAgent2494(VerifyAgent): - pass - - -class VerifyAgent2495(VerifyAgent2493): - pass - - -class VerificationItem1247(VerificationItem): - pass - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class DeclaredBy1254(DeclaredBy): - pass - - -class VerifyAgent2496(VerifyAgent): - pass - - -class VerifyAgent2497(VerifyAgent2493): - pass - - -class VerificationItem1248(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy1255(DeclaredBy): - pass - - -class VerifyAgent2498(VerifyAgent): - pass - - -class VerifyAgent2499(VerifyAgent2493): - pass - - -class VerificationItem1249(VerificationItem): - pass - - -class DeclaredBy1256(DeclaredBy): - pass - - -class VerifyAgent2500(VerifyAgent): - pass - - -class VerifyAgent2501(VerifyAgent2493): - pass - - -class VerificationItem1250(VerificationItem): - pass - - -class DeclaredBy1257(DeclaredBy): - pass - - -class VerifyAgent2502(VerifyAgent): - pass - - -class VerifyAgent2503(VerifyAgent2493): - pass - - -class VerificationItem1251(VerificationItem): - pass - - -class DeclaredBy1258(DeclaredBy): - pass - - -class VerifyAgent2504(VerifyAgent): - pass - - -class VerifyAgent2505(VerifyAgent2493): - pass - - -class VerificationItem1252(VerificationItem): - pass - - -class DeclaredBy1259(DeclaredBy): - pass - - -class VerifyAgent2506(VerifyAgent): - pass - - -class VerifyAgent2507(VerifyAgent2493): - pass - - -class VerificationItem1253(VerificationItem): - pass - - -class DeclaredBy1260(DeclaredBy): - pass - - -class VerifyAgent2508(VerifyAgent): - pass - - -class VerifyAgent2509(VerifyAgent2493): - pass - - -class VerificationItem1254(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy1261(DeclaredBy): - pass - - -class VerifyAgent2510(VerifyAgent): - pass - - -class VerifyAgent2511(VerifyAgent2493): - pass - - -class VerificationItem1255(VerificationItem): - pass - - -class DeclaredBy1262(DeclaredBy): - pass - - -class VerifyAgent2512(VerifyAgent): - pass - - -class VerifyAgent2513(VerifyAgent2493): - pass - - -class VerificationItem1256(VerificationItem): - pass - - -class DeclaredBy1263(DeclaredBy): - pass - - -class VerifyAgent2514(VerifyAgent): - pass - - -class VerifyAgent2515(VerifyAgent2493): - pass - - -class VerificationItem1257(VerificationItem): - pass - - -class DeclaredBy1264(DeclaredBy): - pass - - -class VerifyAgent2516(VerifyAgent): - pass - - -class VerifyAgent2517(VerifyAgent2493): - pass - - -class VerificationItem1258(VerificationItem): - pass - - -class DeclaredBy1265(DeclaredBy): - pass - - -class VerifyAgent2518(VerifyAgent): - pass - - -class VerifyAgent2519(VerifyAgent2493): - pass - - -class VerificationItem1259(VerificationItem): - pass - - -class DeclaredBy1266(DeclaredBy): - pass - - -class VerifyAgent2520(VerifyAgent): - pass - - -class VerifyAgent2521(VerifyAgent2493): - pass - - -class VerificationItem1260(VerificationItem): - pass - - -class DeclaredBy1267(DeclaredBy): - pass - - -class VerifyAgent2522(VerifyAgent): - pass - - -class VerifyAgent2523(VerifyAgent2493): - pass - - -class VerificationItem1261(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role1336(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role1336, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy1268(DeclaredBy): - pass - - -class VerifyAgent2524(VerifyAgent): - pass - - -class VerifyAgent2525(VerifyAgent2493): - pass - - -class VerificationItem1262(VerificationItem): - pass - - -class DeclaredBy1269(DeclaredBy): - pass - - -class VerifyAgent2526(VerifyAgent): - pass - - -class VerifyAgent2527(VerifyAgent2493): - pass - - -class VerificationItem1263(VerificationItem): - pass - - -class DeclaredBy1270(DeclaredBy): - pass - - -class VerifyAgent2528(VerifyAgent): - pass - - -class VerifyAgent2529(VerifyAgent2493): - pass - - -class VerificationItem1264(VerificationItem): - pass - - -class DeclaredBy1271(DeclaredBy): - pass - - -class VerifyAgent2530(VerifyAgent): - pass - - -class VerifyAgent2531(VerifyAgent2493): - pass - - -class VerificationItem1265(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy1272(DeclaredBy): - pass - - -class VerifyAgent2532(VerifyAgent): - pass - - -class VerifyAgent2533(VerifyAgent2493): - pass - - -class VerificationItem1266(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy1273(DeclaredBy): - pass - - -class VerifyAgent2534(VerifyAgent): - pass - - -class VerifyAgent2535(VerifyAgent2493): - pass - - -class VerificationItem1267(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy1274(DeclaredBy): - pass - - -class VerifyAgent2536(VerifyAgent): - pass - - -class VerifyAgent2537(VerifyAgent2493): - pass - - -class VerificationItem1268(VerificationItem): - pass - - -class Target129(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy1275(DeclaredBy): - pass - - -class VerifyAgent2538(VerifyAgent): - pass - - -class VerifyAgent2539(VerifyAgent2493): - pass - - -class VerificationItem1269(VerificationItem): - pass - - -class DeclaredBy1276(DeclaredBy): - pass - - -class VerifyAgent2540(VerifyAgent): - pass - - -class VerifyAgent2541(VerifyAgent2493): - pass - - -class VerificationItem1270(VerificationItem): - pass - - -class DeclaredBy1277(DeclaredBy): - pass - - -class VerifyAgent2542(VerifyAgent): - pass - - -class VerifyAgent2543(VerifyAgent2493): - pass - - -class VerificationItem1271(VerificationItem): - pass - - -class DeclaredBy1278(DeclaredBy): - pass - - -class VerifyAgent2544(VerifyAgent): - pass - - -class VerifyAgent2545(VerifyAgent2493): - pass - - -class VerificationItem1272(VerificationItem): - pass - - -class DeclaredBy1279(DeclaredBy): - pass - - -class VerifyAgent2546(VerifyAgent): - pass - - -class VerifyAgent2547(VerifyAgent2493): - pass - - -class VerificationItem1273(VerificationItem): - pass - - -class DeclaredBy1280(DeclaredBy): - pass - - -class VerifyAgent2548(VerifyAgent): - pass - - -class VerifyAgent2549(VerifyAgent2493): - pass - - -class VerificationItem1274(VerificationItem): - pass - - -class DeclaredBy1281(DeclaredBy): - pass - - -class VerifyAgent2550(VerifyAgent): - pass - - -class VerifyAgent2551(VerifyAgent2493): - pass - - -class VerificationItem1275(VerificationItem): - pass - - -class DeclaredBy1282(DeclaredBy): - pass - - -class VerifyAgent2552(VerifyAgent): - pass - - -class VerifyAgent2553(VerifyAgent2493): - pass - - -class VerificationItem1276(VerificationItem): - pass - - -class DeclaredBy1283(DeclaredBy): - pass - - -class VerifyAgent2554(VerifyAgent): - pass - - -class VerifyAgent2555(VerifyAgent2493): - pass - - -class VerificationItem1277(VerificationItem): - pass - - -class DeclaredBy1284(DeclaredBy): - pass - - -class VerifyAgent2556(VerifyAgent): - pass - - -class VerifyAgent2557(VerifyAgent2493): - pass - - -class VerificationItem1278(VerificationItem): - pass - - -class DeclaredBy1285(DeclaredBy): - pass - - -class VerifyAgent2558(VerifyAgent): - pass - - -class VerifyAgent2559(VerifyAgent2493): - pass - - -class VerificationItem1279(VerificationItem): - pass - - -class DeclaredBy1286(DeclaredBy): - pass - - -class VerifyAgent2560(VerifyAgent): - pass - - -class VerifyAgent2561(VerifyAgent2493): - pass - - -class VerificationItem1280(VerificationItem): - pass - - -class DeclaredBy1287(DeclaredBy): - pass - - -class VerifyAgent2562(VerifyAgent): - pass - - -class VerifyAgent2563(VerifyAgent2493): - pass - - -class VerificationItem1281(VerificationItem): - pass - - -class DeclaredBy1288(DeclaredBy): - pass - - -class VerifyAgent2564(VerifyAgent): - pass - - -class VerifyAgent2565(VerifyAgent2493): - pass - - -class VerificationItem1282(VerificationItem): - pass - - -class DeclaredBy1289(DeclaredBy): - pass - - -class VerifyAgent2566(VerifyAgent): - pass - - -class VerifyAgent2567(VerifyAgent2493): - pass - - -class VerificationItem1283(VerificationItem): - pass - - -class ReferenceAsset48(ReferenceAsset): - pass - - -class FeedFieldMapping51(FeedFieldMapping): - pass - - -class ReferenceAuthorization48(ReferenceAuthorization): - pass - - -class DeclaredBy1290(DeclaredBy): - pass - - -class VerifyAgent2568(VerifyAgent): - pass - - -class VerifyAgent2569(VerifyAgent2493): - pass - - -class VerificationItem1284(VerificationItem): - pass - - -class DeclaredBy1291(DeclaredBy): - pass - - -class VerifyAgent2570(VerifyAgent): - pass - - -class VerifyAgent2571(VerifyAgent2493): - pass - - -class VerificationItem1285(VerificationItem): - pass - - -class DeclaredBy1292(DeclaredBy): - pass - - -class VerifyAgent2572(VerifyAgent): - pass - - -class VerifyAgent2573(VerifyAgent2493): - pass - - -class VerificationItem1286(VerificationItem): - pass - - -class DeclaredBy1293(DeclaredBy): - pass - - -class VerifyAgent2574(VerifyAgent): - pass - - -class VerifyAgent2575(VerifyAgent2493): - pass - - -class VerificationItem1287(VerificationItem): - pass - - -class DeclaredBy1294(DeclaredBy): - pass - - -class VerifyAgent2576(VerifyAgent): - pass - - -class VerifyAgent2577(VerifyAgent2493): - pass - - -class VerificationItem1288(VerificationItem): - pass - - -class DeclaredBy1295(DeclaredBy): - pass - - -class VerifyAgent2578(VerifyAgent): - pass - - -class VerifyAgent2579(VerifyAgent2493): - pass - - -class VerificationItem1289(VerificationItem): - pass - - -class DeclaredBy1296(DeclaredBy): - pass - - -class VerifyAgent2580(VerifyAgent): - pass - - -class VerifyAgent2581(VerifyAgent2493): - pass - - -class VerificationItem1290(VerificationItem): - pass - - -class DeclaredBy1297(DeclaredBy): - pass - - -class VerifyAgent2582(VerifyAgent): - pass - - -class VerifyAgent2583(VerifyAgent2493): - pass - - -class VerificationItem1291(VerificationItem): - pass - - -class DeclaredBy1298(DeclaredBy): - pass - - -class VerifyAgent2584(VerifyAgent): - pass - - -class VerifyAgent2585(VerifyAgent2493): - pass - - -class VerificationItem1292(VerificationItem): - pass - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Use(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ExcludedCountry(Country): - pass - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class Right(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[Use], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: Annotated[ - RightType | None, - Field( - description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.', - title='Right Type', - ), - ] = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Type(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy1299(DeclaredBy): - pass - - -class VerifyAgent2586(VerifyAgent): - pass - - -class VerifyAgent2587(VerifyAgent2493): - pass - - -class VerificationItem1293(VerificationItem): - pass - - - -class DeclaredBy1300(DeclaredBy): - pass - - -class VerifyAgent2588(VerifyAgent): - pass - - -class VerifyAgent2589(VerifyAgent2493): - pass - - -class VerificationItem1294(VerificationItem): - pass - - -class DeclaredBy1301(DeclaredBy): - pass - - -class VerifyAgent2590(VerifyAgent): - pass - - -class VerifyAgent2591(VerifyAgent2493): - pass - - -class VerificationItem1295(VerificationItem): - pass - - -class DeclaredBy1302(DeclaredBy): - pass - - -class VerifyAgent2592(VerifyAgent): - pass - - -class VerifyAgent2593(VerifyAgent2493): - pass - - -class VerificationItem1296(VerificationItem): - pass - - -class DeclaredBy1303(DeclaredBy): - pass - - -class VerifyAgent2594(VerifyAgent): - pass - - -class VerifyAgent2595(VerifyAgent2493): - pass - - -class VerificationItem1297(VerificationItem): - pass - - -class DeclaredBy1304(DeclaredBy): - pass - - -class VerifyAgent2596(VerifyAgent): - pass - - -class VerifyAgent2597(VerifyAgent2493): - pass - - -class VerificationItem1298(VerificationItem): - pass - - -class DeclaredBy1305(DeclaredBy): - pass - - -class VerifyAgent2598(VerifyAgent): - pass - - -class VerifyAgent2599(VerifyAgent2493): - pass - - -class VerificationItem1299(VerificationItem): - pass - - -class DeclaredBy1306(DeclaredBy): - pass - - -class VerifyAgent2600(VerifyAgent): - pass - - -class VerifyAgent2601(VerifyAgent2493): - pass - - -class VerificationItem1300(VerificationItem): - pass - - -class DeclaredBy1307(DeclaredBy): - pass - - -class VerifyAgent2602(VerifyAgent): - pass - - -class VerifyAgent2603(VerifyAgent2493): - pass - - -class VerificationItem1301(VerificationItem): - pass - - -class DeclaredBy1308(DeclaredBy): - pass - - -class VerifyAgent2604(VerifyAgent): - pass - - -class VerifyAgent2605(VerifyAgent2493): - pass - - -class VerificationItem1302(VerificationItem): - pass - - -class DeclaredBy1309(DeclaredBy): - pass - - -class VerifyAgent2606(VerifyAgent): - pass - - -class VerifyAgent2607(VerifyAgent2493): - pass - - -class VerificationItem1303(VerificationItem): - pass - - -class DeclaredBy1310(DeclaredBy): - pass - - -class VerifyAgent2608(VerifyAgent): - pass - - -class VerifyAgent2609(VerifyAgent2493): - pass - - -class VerificationItem1304(VerificationItem): - pass - - -class DeclaredBy1311(DeclaredBy): - pass - - -class VerifyAgent2610(VerifyAgent): - pass - - -class VerifyAgent2611(VerifyAgent2493): - pass - - -class VerificationItem1305(VerificationItem): - pass - - -class DeclaredBy1312(DeclaredBy): - pass - - -class VerifyAgent2612(VerifyAgent): - pass - - -class VerifyAgent2613(VerifyAgent2493): - pass - - -class VerificationItem1306(VerificationItem): - pass - - -class DeclaredBy1313(DeclaredBy): - pass - - -class VerifyAgent2614(VerifyAgent): - pass - - -class VerifyAgent2615(VerifyAgent2493): - pass - - -class VerificationItem1307(VerificationItem): - pass - - -class ReferenceAsset49(ReferenceAsset): - pass - - -class FeedFieldMapping52(FeedFieldMapping): - pass - - -class ReferenceAuthorization49(ReferenceAuthorization): - pass - - -class DeclaredBy1314(DeclaredBy): - pass - - -class VerifyAgent2616(VerifyAgent): - pass - - -class VerifyAgent2617(VerifyAgent2493): - pass - - -class VerificationItem1308(VerificationItem): - pass - - -class DeclaredBy1315(DeclaredBy): - pass - - -class VerifyAgent2618(VerifyAgent): - pass - - -class VerifyAgent2619(VerifyAgent2493): - pass - - -class VerificationItem1309(VerificationItem): - pass - - -class DeclaredBy1316(DeclaredBy): - pass - - -class VerifyAgent2620(VerifyAgent): - pass - - -class VerifyAgent2621(VerifyAgent2493): - pass - - -class VerificationItem1310(VerificationItem): - pass - - -class DeclaredBy1317(DeclaredBy): - pass - - -class VerifyAgent2622(VerifyAgent): - pass - - -class VerifyAgent2623(VerifyAgent2493): - pass - - -class VerificationItem1311(VerificationItem): - pass - - -class DeclaredBy1318(DeclaredBy): - pass - - -class VerifyAgent2624(VerifyAgent): - pass - - -class VerifyAgent2625(VerifyAgent2493): - pass - - -class VerificationItem1312(VerificationItem): - pass - - -class DeclaredBy1319(DeclaredBy): - pass - - -class VerifyAgent2626(VerifyAgent): - pass - - -class VerifyAgent2627(VerifyAgent2493): - pass - - -class VerificationItem1313(VerificationItem): - pass - - -class DeclaredBy1320(DeclaredBy): - pass - - -class VerifyAgent2628(VerifyAgent): - pass - - -class VerifyAgent2629(VerifyAgent2493): - pass - - -class VerificationItem1314(VerificationItem): - pass - - -class DeclaredBy1321(DeclaredBy): - pass - - -class VerifyAgent2630(VerifyAgent): - pass - - -class VerifyAgent2631(VerifyAgent2493): - pass - - -class VerificationItem1315(VerificationItem): - pass - - -class DeclaredBy1322(DeclaredBy): - pass - - -class VerifyAgent2632(VerifyAgent): - pass - - -class VerifyAgent2633(VerifyAgent2493): - pass - - -class VerificationItem1316(VerificationItem): - pass - - -class DeclaredBy1323(DeclaredBy): - pass - - -class VerifyAgent2634(VerifyAgent): - pass - - -class VerifyAgent2635(VerifyAgent2493): - pass - - -class VerificationItem1317(VerificationItem): - pass - - -class DeclaredBy1324(DeclaredBy): - pass - - -class VerifyAgent2636(VerifyAgent): - pass - - -class VerifyAgent2637(VerifyAgent2493): - pass - - -class VerificationItem1318(VerificationItem): - pass - - -class DeclaredBy1325(DeclaredBy): - pass - - -class VerifyAgent2638(VerifyAgent): - pass - - -class VerifyAgent2639(VerifyAgent2493): - pass - - -class VerificationItem1319(VerificationItem): - pass - - -class DeclaredBy1326(DeclaredBy): - pass - - -class VerifyAgent2640(VerifyAgent): - pass - - -class VerifyAgent2641(VerifyAgent2493): - pass - - -class VerificationItem1320(VerificationItem): - pass - - -class DeclaredBy1327(DeclaredBy): - pass - - -class VerifyAgent2642(VerifyAgent): - pass - - -class VerifyAgent2643(VerifyAgent2493): - pass - - -class VerificationItem1321(VerificationItem): - pass - - -class DeclaredBy1328(DeclaredBy): - pass - - -class VerifyAgent2644(VerifyAgent): - pass - - -class VerifyAgent2645(VerifyAgent2493): - pass - - -class VerificationItem1322(VerificationItem): - pass - - -class DeclaredBy1329(DeclaredBy): - pass - - -class VerifyAgent2646(VerifyAgent): - pass - - -class VerifyAgent2647(VerifyAgent2493): - pass - - -class VerificationItem1323(VerificationItem): - pass - - -class DeclaredBy1330(DeclaredBy): - pass - - -class VerifyAgent2648(VerifyAgent): - pass - - -class VerifyAgent2649(VerifyAgent2493): - pass - - -class VerificationItem1324(VerificationItem): - pass - - -class DeclaredBy1331(DeclaredBy): - pass - - -class VerifyAgent2650(VerifyAgent): - pass - - -class VerifyAgent2651(VerifyAgent2493): - pass - - -class VerificationItem1325(VerificationItem): - pass - - -class DeclaredBy1332(DeclaredBy): - pass - - -class VerifyAgent2652(VerifyAgent): - pass - - -class VerifyAgent2653(VerifyAgent2493): - pass - - -class VerificationItem1326(VerificationItem): - pass - - -class DeclaredBy1333(DeclaredBy): - pass - - -class VerifyAgent2654(VerifyAgent): - pass - - -class VerifyAgent2655(VerifyAgent2493): - pass - - -class VerificationItem1327(VerificationItem): - pass - - -class DeclaredBy1334(DeclaredBy): - pass - - -class VerifyAgent2656(VerifyAgent): - pass - - -class VerifyAgent2657(VerifyAgent2493): - pass - - -class VerificationItem1328(VerificationItem): - pass - - -class DeclaredBy1335(DeclaredBy): - pass - - -class VerifyAgent2658(VerifyAgent): - pass - - -class VerifyAgent2659(VerifyAgent2493): - pass - - -class VerificationItem1329(VerificationItem): - pass - - -class ReferenceAsset50(ReferenceAsset): - pass - - -class FeedFieldMapping53(FeedFieldMapping): - pass - - -class ReferenceAuthorization50(ReferenceAuthorization): - pass - - -class DeclaredBy1336(DeclaredBy): - pass - - -class VerifyAgent2660(VerifyAgent): - pass - - -class VerifyAgent2661(VerifyAgent2493): - pass - - -class VerificationItem1330(VerificationItem): - pass - - -class DeclaredBy1337(DeclaredBy): - pass - - -class VerifyAgent2662(VerifyAgent): - pass - - -class VerifyAgent2663(VerifyAgent2493): - pass - - -class VerificationItem1331(VerificationItem): - pass - - -class DeclaredBy1338(DeclaredBy): - pass - - -class VerifyAgent2664(VerifyAgent): - pass - - -class VerifyAgent2665(VerifyAgent2493): - pass - - -class VerificationItem1332(VerificationItem): - pass - - -class DeclaredBy1339(DeclaredBy): - pass - - -class VerifyAgent2666(VerifyAgent): - pass - - -class VerifyAgent2667(VerifyAgent2493): - pass - - -class VerificationItem1333(VerificationItem): - pass - - -class DeclaredBy1340(DeclaredBy): - pass - - -class VerifyAgent2668(VerifyAgent): - pass - - -class VerifyAgent2669(VerifyAgent2493): - pass - - -class VerificationItem1334(VerificationItem): - pass - - -class DeclaredBy1341(DeclaredBy): - pass - - -class VerifyAgent2670(VerifyAgent): - pass - - -class VerifyAgent2671(VerifyAgent2493): - pass - - -class VerificationItem1335(VerificationItem): - pass - - -class DeclaredBy1342(DeclaredBy): - pass - - -class VerifyAgent2672(VerifyAgent): - pass - - -class VerifyAgent2673(VerifyAgent2493): - pass - - -class VerificationItem1336(VerificationItem): - pass - - -class DeclaredBy1343(DeclaredBy): - pass - - -class VerifyAgent2674(VerifyAgent): - pass - - -class VerifyAgent2675(VerifyAgent2493): - pass - - -class VerificationItem1337(VerificationItem): - pass - - -class DeclaredBy1344(DeclaredBy): - pass - - -class VerifyAgent2676(VerifyAgent): - pass - - -class VerifyAgent2677(VerifyAgent2493): - pass - - -class VerificationItem1338(VerificationItem): - pass - - -class Right27(Right): - pass - - -class IndustryIdentifier24(IndustryIdentifier): - pass - - -class DeclaredBy1345(DeclaredBy): - pass - - -class VerifyAgent2678(VerifyAgent): - pass - - -class VerifyAgent2679(VerifyAgent2493): - pass - - -class VerificationItem1339(VerificationItem): - pass - - -class Targets(AdCPBaseModel): - kind: Literal['canonical'] = 'canonical' - id: Annotated[ - str, - Field( - description="Canonical format name from `canonical-format-kind.json` (e.g., `image`, `video_hosted`, `audio_daast`). Validators check the manifest against the canonical's parameter schema." - ), - ] - - -class Targets6(AdCPBaseModel): - kind: Literal['product'] = 'product' - id: Annotated[ - str, - Field( - description="Product ID. Validators check the manifest against the product's inline `ProductFormatDeclaration` narrowing of the canonical (parameter constraints, slot requirements, platform extensions)." - ), - ] - - -class Targets7(AdCPBaseModel): - kind: Literal['third_party_format'] = 'third_party_format' - id: Annotated[ - AnyUrl, - Field( - description='URI-form format identifier referencing a third-party format definition (e.g., `https://flashtalking.example/formats/image_300x250@sha256:...`). Validators fetch the definition (digest-pinned, cached) and validate the manifest against it.' - ), - ] - - -class Targets4(RootModel[Targets | Targets6 | Targets7]): - root: Annotated[Targets | Targets6 | Targets7, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2493 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1303] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account61(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class EmbeddedProvenanceItem1247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2494 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2495 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1304(Jurisdiction1303): - pass - - -class Disclosure1250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1304] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1253 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1247] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1247] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1250 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1247] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1246 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo160 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand63(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride159 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem1248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2496 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2497 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1305(Jurisdiction1303): - pass - - -class Disclosure1251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1305] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1254 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1248] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1248] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1251 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1248] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1247 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2498 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2499 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1306(Jurisdiction1303): - pass - - -class Disclosure1252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1306] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1255 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1249] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1249] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1252 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1249] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets630(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1248 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2500 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2501 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1307(Jurisdiction1303): - pass - - -class Disclosure1253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1307] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1256 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1250] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1250] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1253 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1250] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets631(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1249 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2502 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2503 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1308(Jurisdiction1303): - pass - - -class Disclosure1254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1308] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1257 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1251] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1251] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1254 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1251] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets632(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1250 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2504 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2505 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1309(Jurisdiction1303): - pass - - -class Disclosure1255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1309] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1258 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1252] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1252] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1255 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1252] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets633(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1251 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2506 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2507 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1310(Jurisdiction1303): - pass - - -class Disclosure1256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1310] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1259 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1253] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1253] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1256 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1253] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets634(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1252 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2508 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2509 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1311(Jurisdiction1303): - pass - - -class Disclosure1257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1311] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1260 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1254] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1254] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1257 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1254] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets635(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1253 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2510 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2511 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1312(Jurisdiction1303): - pass - - -class Disclosure1258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1312] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1261 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1255] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1255] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1258 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1255] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets636(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1254 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2512 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2513 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1313(Jurisdiction1303): - pass - - -class Disclosure1259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1313] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1262 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1256] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1256] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1259 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1256] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets637(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1255 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2514 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2515 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1314(Jurisdiction1303): - pass - - -class Disclosure1260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1314] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1263 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1257] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1257] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1260 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1257] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets638(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1256 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem1258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2516 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2517 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1315(Jurisdiction1303): - pass - - -class Disclosure1261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1315] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1264 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1258] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1258] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1261 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1258] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets639(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1257 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2518 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2519 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1316(Jurisdiction1303): - pass - - -class Disclosure1262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1316] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1265 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1259] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1259] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1262 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1259] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets640(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1258 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2520 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2521 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1317(Jurisdiction1303): - pass - - -class Disclosure1263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1317] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1266 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1260] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1260] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1263 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1260] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets641(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1259 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2522 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2523 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1318(Jurisdiction1303): - pass - - -class Disclosure1264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1318] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1267 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1261] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1261] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1264 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1261] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets642(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1260 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets643(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets644(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets645(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2524 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2525 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1320(Jurisdiction1303): - pass - - -class Disclosure1265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1320] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1268 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1262] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1262] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1265 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1262] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets646(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1261 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2526 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2527 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1321(Jurisdiction1303): - pass - - -class Disclosure1266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1321] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1269 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1263] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1263] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1266 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1263] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1262 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2528 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2529 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1322(Jurisdiction1303): - pass - - -class Disclosure1267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1322] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1270 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1264] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1264] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1267 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1264] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1263 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2530 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2531 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1323(Jurisdiction1303): - pass - - -class Disclosure1268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1323] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1271 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1265] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1265] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1268 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1265] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1264 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2532 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2533 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1324(Jurisdiction1303): - pass - - -class Disclosure1269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1324] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1272 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1266] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1266] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1269 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1266] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets647(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media93, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1265 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2534 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2535 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1325(Jurisdiction1303): - pass - - -class Disclosure1270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1325] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1273 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1267] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1267] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1270 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1267] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets648(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1266 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2536 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2537 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1326(Jurisdiction1303): - pass - - -class Disclosure1271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1326] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1274 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1268] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1268] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1271 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1268] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets649(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance1267 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2538 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2539 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1327(Jurisdiction1303): - pass - - -class Disclosure1272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1327] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1275 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1269] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1269] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1272 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1269] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets650(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target129 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target129.linear - provenance: Annotated[ - Provenance1268 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2540 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2541 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1328(Jurisdiction1303): - pass - - -class Disclosure1273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1328] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1276 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1270] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1270] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1273 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1270] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1269 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2542 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2543 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1329(Jurisdiction1303): - pass - - -class Disclosure1274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1329] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1277 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1271] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1271] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1274 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1271] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1270 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2544 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2545 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1330(Jurisdiction1303): - pass - - -class Disclosure1275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1330] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1278 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1272] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1272] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1275 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1272] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1271 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2546 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2547 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1331(Jurisdiction1303): - pass - - -class Disclosure1276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1331] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1279 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1273] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1273] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1276 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1273] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1272 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2548 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2549 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1332(Jurisdiction1303): - pass - - -class Disclosure1277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1332] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1280 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1274] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1274] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1277 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1274] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1273 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2550 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2551 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1333(Jurisdiction1303): - pass - - -class Disclosure1278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1333] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1281 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1275] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1275] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1278 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1275] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1274 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2552 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2553 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1334(Jurisdiction1303): - pass - - -class Disclosure1279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1334] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1282 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1276] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1276] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1279 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1276] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1275 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2554 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2555 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1335(Jurisdiction1303): - pass - - -class Disclosure1280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1335] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1283 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1277] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1277] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1280 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1277] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1276 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2556 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2557 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1336(Jurisdiction1303): - pass - - -class Disclosure1281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1336] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1284 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1278] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1278] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1281 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1278] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1277 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2558 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2559 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1337(Jurisdiction1303): - pass - - -class Disclosure1282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1337] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1285 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1279] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1279] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1282 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1279] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1278 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2560 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2561 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1338(Jurisdiction1303): - pass - - -class Disclosure1283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1338] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1286 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1280] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1280] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1283 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1280] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1279 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2562 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2563 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1339(Jurisdiction1303): - pass - - -class Disclosure1284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1339] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1287 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1281] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1281] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1284 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1281] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1280 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2564 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2565 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1340(Jurisdiction1303): - pass - - -class Disclosure1285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1340] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1288 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1282] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1282] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1285 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1282] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1281 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2566 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2567 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1341(Jurisdiction1303): - pass - - -class Disclosure1286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1341] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1289 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1283] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1283] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1286 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1283] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1282 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure48(RequiredDisclosure): - pass - - -class Compliance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure48] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets65117(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset48] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance48 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets65118(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping51] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2568 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2569 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1343(Jurisdiction1303): - pass - - -class Disclosure1287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1343] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1290 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1284] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1284] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1287 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1284] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization48 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1283 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2570 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2571 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1344(Jurisdiction1303): - pass - - -class Disclosure1288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1344] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1291 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1285] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1285] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1288 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1285] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1284 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2572 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2573 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1345(Jurisdiction1303): - pass - - -class Disclosure1289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1345] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1292 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1286] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1286] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1289 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1286] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1285 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2574 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2575 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1346(Jurisdiction1303): - pass - - -class Disclosure1290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1346] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1293 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1287] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1287] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1290 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1287] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1286 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2576 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2577 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1347(Jurisdiction1303): - pass - - -class Disclosure1291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1347] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1294 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1288] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1288] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1291 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1288] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media94 | Media95, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl47 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1287 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2578 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2579 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1348(Jurisdiction1303): - pass - - -class Disclosure1292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1348] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1295 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1289] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1289] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1292 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1289] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1288 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2580 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2581 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1349(Jurisdiction1303): - pass - - -class Disclosure1293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1349] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1296 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1290] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1290] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1293 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1290] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance1289 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2582 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2583 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1350(Jurisdiction1303): - pass - - -class Disclosure1294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1350] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1297 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1291] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1291] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1294 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1291] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target129 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target129.linear - provenance: Annotated[ - Provenance1290 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets6511( - RootModel[ - Assets6512 - | Assets6513 - | Assets6514 - | Assets6515 - | Assets6516 - | Assets6517 - | Assets6518 - | Assets6519 - | Assets65110 - | Assets65111 - | Assets65112 - | Assets65113 - | Assets65114 - | Assets65115 - | Assets643 - | Assets65117 - | Assets65118 - | Assets65119 - | Assets65120 - | Assets65121 - | Assets65122 - | Assets65123 - ] -): - root: Annotated[ - Assets6512 - | Assets6513 - | Assets6514 - | Assets6515 - | Assets6516 - | Assets6517 - | Assets6518 - | Assets6519 - | Assets65110 - | Assets65111 - | Assets65112 - | Assets65113 - | Assets65114 - | Assets65115 - | Assets643 - | Assets65117 - | Assets65118 - | Assets65119 - | Assets65120 - | Assets65121 - | Assets65122 - | Assets65123, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets651(RootModel[list[Assets6511]]): - root: Annotated[list[Assets6511], Field(min_length=1)] - - -class EmbeddedProvenanceItem1292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2584 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2585 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1351(Jurisdiction1303): - pass - - -class Disclosure1295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1351] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1298 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1292] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1292] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1295 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1292] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1291 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo161 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand64(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride160 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem1293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2586 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2587 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1352(Jurisdiction1303): - pass - - -class Disclosure1296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1352] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1299 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1293] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1293] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1296 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1293] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Manifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: Annotated[ - FormatKind | None, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef45 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets630 - | Assets631 - | Assets632 - | Assets633 - | Assets634 - | Assets635 - | Assets636 - | Assets637 - | Assets638 - | Assets639 - | Assets640 - | Assets641 - | Assets642 - | Assets643 - | Assets644 - | Assets645 - | Assets646 - | Assets647 - | Assets648 - | Assets649 - | Assets650 - | Assets651, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand64 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1292 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem1294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2588 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2589 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1353(Jurisdiction1303): - pass - - -class Disclosure1297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1353] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1300 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1294] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1294] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1297 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1294] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets652(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1293 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2590 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2591 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1354(Jurisdiction1303): - pass - - -class Disclosure1298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1354] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1301 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1295] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1295] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1298 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1295] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets653(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1294 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2592 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2593 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1355(Jurisdiction1303): - pass - - -class Disclosure1299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1355] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1302 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1296] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1296] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1299 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1296] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets654(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1295 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2594 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2595 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1356(Jurisdiction1303): - pass - - -class Disclosure1300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1356] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1303 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1297] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1297] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1300 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1297] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets655(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1296 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2596 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2597 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1357(Jurisdiction1303): - pass - - -class Disclosure1301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1357] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1304 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1298] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1298] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1301 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1298] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets656(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1297 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2598 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2599 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1358(Jurisdiction1303): - pass - - -class Disclosure1302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1358] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1305 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1299] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1299] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1302 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1299] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets657(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1298 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2600 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2601 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1359(Jurisdiction1303): - pass - - -class Disclosure1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1359] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1306 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1300] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1300] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1303 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1300] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets658(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1299 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2602 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2603 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1360(Jurisdiction1303): - pass - - -class Disclosure1304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1360] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1307 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1301] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1301] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1304 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1301] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets659(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1300 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2604 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2605 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1361(Jurisdiction1303): - pass - - -class Disclosure1305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1361] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1308 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1302] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1302] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1305 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1302] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets660(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1301 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2606 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2607 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1362(Jurisdiction1303): - pass - - -class Disclosure1306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1362] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1309 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1303] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1303] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1306 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1303] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets661(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1302 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2608 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2609 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1363(Jurisdiction1303): - pass - - -class Disclosure1307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1363] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1310 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1304] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1304] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1307 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1304] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets662(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1303 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2610 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2611 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1364(Jurisdiction1303): - pass - - -class Disclosure1308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1364] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1311 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1305] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1305] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1308 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1305] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets663(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1304 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2612 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2613 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1365(Jurisdiction1303): - pass - - -class Disclosure1309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1365] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1312 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1306] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1306] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1309 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1306] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets664(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1305 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2614 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2615 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1366(Jurisdiction1303): - pass - - -class Disclosure1310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1366] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1313 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1307] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1307] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1310 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1307] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets665(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1306 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets666(Assets643): - pass - - -class RequiredDisclosure49(RequiredDisclosure): - pass - - -class Compliance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure49] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets667(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset49] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance49 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets668(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping52] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2616 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2617 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1368(Jurisdiction1303): - pass - - -class Disclosure1311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1368] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1314 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1308] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1308] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1311 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1308] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets669(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization49 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1307 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2618 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2619 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1369(Jurisdiction1303): - pass - - -class Disclosure1312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1369] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1315 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1309] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1309] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1312 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1309] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1308 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2620 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2621 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1370(Jurisdiction1303): - pass - - -class Disclosure1313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1370] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1316 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1310] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1310] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1313 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1310] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1309 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2622 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2623 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1371(Jurisdiction1303): - pass - - -class Disclosure1314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1371] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1317 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1311] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1311] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1314 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1311] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1310 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2624 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2625 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1372(Jurisdiction1303): - pass - - -class Disclosure1315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1372] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1318 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1312] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1312] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1315 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1312] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets670(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media96 | Media97, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl48 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1311 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2626 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2627 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1373(Jurisdiction1303): - pass - - -class Disclosure1316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1373] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1319 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1313] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1313] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1316 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1313] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets671(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1312 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2628 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2629 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1374(Jurisdiction1303): - pass - - -class Disclosure1317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1374] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1320 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1314] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1314] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1317 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1314] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets672(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance1313 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2630 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2631 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1375(Jurisdiction1303): - pass - - -class Disclosure1318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1375] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1321 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1315] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1315] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1318 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1315] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets673(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target129 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target129.linear - provenance: Annotated[ - Provenance1314 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2632 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2633 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1376(Jurisdiction1303): - pass - - -class Disclosure1319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1376] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1322 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1316] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1316] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1319 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1316] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6742(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1315 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2634 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2635 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1377(Jurisdiction1303): - pass - - -class Disclosure1320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1377] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1323 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1317] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1317] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1320 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1317] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6743(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1316 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2636 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2637 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1378(Jurisdiction1303): - pass - - -class Disclosure1321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1378] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1324 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1318] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1318] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1321 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1318] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6744(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1317 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2638 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2639 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1379(Jurisdiction1303): - pass - - -class Disclosure1322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1379] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1325 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1319] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1319] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1322 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1319] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6745(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1318 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2640 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2641 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1380(Jurisdiction1303): - pass - - -class Disclosure1323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1380] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1326 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1320] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1320] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1323 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1320] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6746(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1319 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem1321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2642 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2643 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1381(Jurisdiction1303): - pass - - -class Disclosure1324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1381] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1327 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1321] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1321] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1324 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1321] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6747(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance1320 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2644 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2645 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1382(Jurisdiction1303): - pass - - -class Disclosure1325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1382] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1328 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1322] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1322] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1325 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1322] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6748(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1321 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2646 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2647 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1383(Jurisdiction1303): - pass - - -class Disclosure1326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1383] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1329 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1323] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1323] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1326 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1323] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6749(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1322 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2648 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2649 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1384(Jurisdiction1303): - pass - - -class Disclosure1327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1384] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1330 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1324] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1324] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1327 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1324] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1323 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2650 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2651 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1385(Jurisdiction1303): - pass - - -class Disclosure1328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1385] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1331 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1325] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1325] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1328 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1325] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance1324 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2652 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2653 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1386(Jurisdiction1303): - pass - - -class Disclosure1329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1386] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1332 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1326] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1326] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1329 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1326] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance1325 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2654 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2655 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1387(Jurisdiction1303): - pass - - -class Disclosure1330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1387] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1333 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1327] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1327] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1330 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1327] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance1326 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2656 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2657 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1388(Jurisdiction1303): - pass - - -class Disclosure1331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1388] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1334 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1328] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1328] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1331 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1328] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1327 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem1329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2658 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2659 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1389(Jurisdiction1303): - pass - - -class Disclosure1332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1389] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1335 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1329] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1329] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1332 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1329] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance1328 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure50(RequiredDisclosure): - pass - - -class Compliance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure50] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets67417(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset50] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance50 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets67418(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping53] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem1330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2660 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2661 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1391(Jurisdiction1303): - pass - - -class Disclosure1333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1391] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1336 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1330] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1330] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1333 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1330] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization50 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance1329 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2662 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2663 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1392(Jurisdiction1303): - pass - - -class Disclosure1334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1392] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1337 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1331] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1331] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1334 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1331] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1330 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2664 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2665 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1393(Jurisdiction1303): - pass - - -class Disclosure1335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1393] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1338 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1332] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1332] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1335 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1332] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1331 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2666 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2667 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1394(Jurisdiction1303): - pass - - -class Disclosure1336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1394] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1339 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1333] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1333] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1336 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1333] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance1332 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2668 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2669 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1395(Jurisdiction1303): - pass - - -class Disclosure1337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1395] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1340 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1334] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1334] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1337 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1334] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media98 | Media99, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl49 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance1333 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2670 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2671 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1396(Jurisdiction1303): - pass - - -class Disclosure1338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1396] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1341 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1335] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1335] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1338 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1335] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance1334 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2672 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2673 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1397(Jurisdiction1303): - pass - - -class Disclosure1339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1397] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1342 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1336] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1336] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1339 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1336] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance1335 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2674 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2675 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1398(Jurisdiction1303): - pass - - -class Disclosure1340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1398] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1343 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1337] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1337] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1340 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1337] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target129 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target129.linear - provenance: Annotated[ - Provenance1336 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets6741( - RootModel[ - Assets6742 - | Assets6743 - | Assets6744 - | Assets6745 - | Assets6746 - | Assets6747 - | Assets6748 - | Assets6749 - | Assets67410 - | Assets67411 - | Assets67412 - | Assets67413 - | Assets67414 - | Assets67415 - | Assets643 - | Assets67417 - | Assets67418 - | Assets67419 - | Assets67420 - | Assets67421 - | Assets67422 - | Assets67423 - ] -): - root: Annotated[ - Assets6742 - | Assets6743 - | Assets6744 - | Assets6745 - | Assets6746 - | Assets6747 - | Assets6748 - | Assets6749 - | Assets67410 - | Assets67411 - | Assets67412 - | Assets67413 - | Assets67414 - | Assets67415 - | Assets643 - | Assets67417 - | Assets67418 - | Assets67419 - | Assets67420 - | Assets67421 - | Assets67422 - | Assets67423, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets674(RootModel[list[Assets6741]]): - root: Annotated[list[Assets6741], Field(min_length=1)] - - -class EmbeddedProvenanceItem1338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2676 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2677 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1399(Jurisdiction1303): - pass - - -class Disclosure1341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1399] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1344 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1338] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1338] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1341 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1338] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1337 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo162 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand65(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride161 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem1339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2678 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2679 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1400(Jurisdiction1303): - pass - - -class Disclosure1342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1400] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1345 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1339] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1339] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1342 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1339] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Manifest3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: Annotated[ - FormatKind, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef45 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets652 - | Assets653 - | Assets654 - | Assets655 - | Assets656 - | Assets657 - | Assets658 - | Assets659 - | Assets660 - | Assets661 - | Assets662 - | Assets663 - | Assets664 - | Assets665 - | Assets666 - | Assets667 - | Assets668 - | Assets669 - | Assets670 - | Assets671 - | Assets672 - | Assets673 - | Assets674, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand65 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right27] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier24] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance1338 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class ValidateInputRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account: Annotated[ - Account | Account61 | None, - Field( - description='Optional account scope for seller-specific product validation. Required by sellers that route product declarations by buyer account.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - brand: Annotated[ - Brand63 | None, - Field( - description='Optional brand scope when account is omitted or the seller keys sandbox validation by brand identity.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - manifest: Annotated[ - Manifest | Manifest3, - Field(description='Creative manifest to validate.', title='Creative Manifest'), - ] - targets: Annotated[ - list[Targets4] | None, - Field( - description="Discriminated list of validation targets. Each entry mirrors the `target` shape on `validate-input-result.json` so the request/response wire shapes match exactly. Multi-target requests enable universal-creative scenarios where one manifest targets multiple sellers' format declarations in a single round-trip; the response carries one result per target in the same order.", - max_length=50, - min_length=1, - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/creative/validate_input_response.py b/src/adcp/types/generated_poc/bundled/creative/validate_input_response.py deleted file mode 100644 index 905bd62d..00000000 --- a/src/adcp/types/generated_poc/bundled/creative/validate_input_response.py +++ /dev/null @@ -1,381 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/creative/validate_input_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Kind(StrEnum): - canonical = 'canonical' - product = 'product' - third_party_format = 'third_party_format' - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Kind - id: Annotated[ - str, - Field( - description="Canonical format name (e.g., 'image'), product_id, or URI-form format_id." - ), - ] - - -class ResultKind(StrEnum): - validated_pass = 'validated_pass' - validated_fail = 'validated_fail' - unvalidatable_nondeterministic = 'unvalidatable_nondeterministic' - - -class Violation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rule: Annotated[ - str, - Field( - description="Rule name (e.g., 'duration_ms_range', 'aspect_ratio', 'max_file_size_kb')." - ), - ] - expected: Annotated[ - Any | None, Field(description="Expected value or range (e.g., '28000-32000', '9:16', 200).") - ] = None - predicted: Annotated[ - Any | None, - Field( - description="Platform's pre-flight estimate for this field (NOT the actual output — there is no protocol state for orphaned out-of-spec artifacts). For TTS, this might be the predicted audio duration from text-length analysis. Helps the buyer fix the input before committing to a build." - ), - ] = None - field: Annotated[ - str, - Field(description="Path to the violating field (e.g., 'assets.video_main.duration_ms')."), - ] - retry_with: Annotated[ - dict[str, Any] | None, - Field( - description='Optional advisory adjustment hint. Platforms MAY suggest a corrected input shape; buyers MUST treat this as advisory, not authoritative.' - ), - ] = None - - -class Result(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - target: Target - result_kind: Annotated[ - ResultKind, - Field( - description="Discriminator for the validation outcome. See schema description for the three states. Replaces the earlier boolean `ok` to distinguish 'failed validation' from 'platform is nondeterministic, can't pre-validate'." - ), - ] - violations: Annotated[ - list[Violation] | None, - Field( - description="When `result_kind` is `validated_fail`, the specific constraints the manifest fails to meet. MUST be absent (or empty) for `validated_pass` and `unvalidatable_nondeterministic` — neither has constraint violations to enumerate (`unvalidatable_nondeterministic` doesn't validate at all; `validated_pass` has nothing to fail)." - ), - ] = None - - -class ValidateInputResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - results: Annotated[list[Result], Field(description='Per-target validation results.')] diff --git a/src/adcp/types/generated_poc/bundled/media_buy/__init__.py b/src/adcp/types/generated_poc/bundled/media_buy/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/src/adcp/types/generated_poc/bundled/media_buy/build_creative_request.py b/src/adcp/types/generated_poc/bundled/media_buy/build_creative_request.py deleted file mode 100644 index fb1f2646..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/build_creative_request.py +++ /dev/null @@ -1,24515 +0,0 @@ -# generated by datamodel-codegen: -# filename: build_creative_request.json -# timestamp: 2026-06-18T11:28:16+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role14(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role14, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction14(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class Target(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class Target1(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent1): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent1): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class DeclaredBy26(DeclaredBy): - pass - - -class VerifyAgent52(VerifyAgent): - pass - - -class VerifyAgent53(VerifyAgent1): - pass - - -class VerificationItem26(VerificationItem): - pass - - -class DeclaredBy27(DeclaredBy): - pass - - -class VerifyAgent54(VerifyAgent): - pass - - -class VerifyAgent55(VerifyAgent1): - pass - - -class VerificationItem27(VerificationItem): - pass - - -class DeclaredBy28(DeclaredBy): - pass - - -class VerifyAgent56(VerifyAgent): - pass - - -class VerifyAgent57(VerifyAgent1): - pass - - -class VerificationItem28(VerificationItem): - pass - - -class DeclaredBy29(DeclaredBy): - pass - - -class VerifyAgent58(VerifyAgent): - pass - - -class VerifyAgent59(VerifyAgent1): - pass - - -class VerificationItem29(VerificationItem): - pass - - -class DeclaredBy30(DeclaredBy): - pass - - -class VerifyAgent60(VerifyAgent): - pass - - -class VerifyAgent61(VerifyAgent1): - pass - - -class VerificationItem30(VerificationItem): - pass - - -class DeclaredBy31(DeclaredBy): - pass - - -class VerifyAgent62(VerifyAgent): - pass - - -class VerifyAgent63(VerifyAgent1): - pass - - -class VerificationItem31(VerificationItem): - pass - - -class DeclaredBy32(DeclaredBy): - pass - - -class VerifyAgent64(VerifyAgent): - pass - - -class VerifyAgent65(VerifyAgent1): - pass - - -class VerificationItem32(VerificationItem): - pass - - -class DeclaredBy33(DeclaredBy): - pass - - -class VerifyAgent66(VerifyAgent): - pass - - -class VerifyAgent67(VerifyAgent1): - pass - - -class VerificationItem33(VerificationItem): - pass - - -class DeclaredBy34(DeclaredBy): - pass - - -class VerifyAgent68(VerifyAgent): - pass - - -class VerifyAgent69(VerifyAgent1): - pass - - -class VerificationItem34(VerificationItem): - pass - - -class DeclaredBy35(DeclaredBy): - pass - - -class VerifyAgent70(VerifyAgent): - pass - - -class VerifyAgent71(VerifyAgent1): - pass - - -class VerificationItem35(VerificationItem): - pass - - -class ReferenceAsset1(ReferenceAsset): - pass - - -class FeedFieldMapping1(FeedFieldMapping): - pass - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class DeclaredBy36(DeclaredBy): - pass - - -class VerifyAgent72(VerifyAgent): - pass - - -class VerifyAgent73(VerifyAgent1): - pass - - -class VerificationItem36(VerificationItem): - pass - - -class DeclaredBy37(DeclaredBy): - pass - - -class VerifyAgent74(VerifyAgent): - pass - - -class VerifyAgent75(VerifyAgent1): - pass - - -class VerificationItem37(VerificationItem): - pass - - -class DeclaredBy38(DeclaredBy): - pass - - -class VerifyAgent76(VerifyAgent): - pass - - -class VerifyAgent77(VerifyAgent1): - pass - - -class VerificationItem38(VerificationItem): - pass - - -class DeclaredBy39(DeclaredBy): - pass - - -class VerifyAgent78(VerifyAgent): - pass - - -class VerifyAgent79(VerifyAgent1): - pass - - -class VerificationItem39(VerificationItem): - pass - - -class DeclaredBy40(DeclaredBy): - pass - - -class VerifyAgent80(VerifyAgent): - pass - - -class VerifyAgent81(VerifyAgent1): - pass - - -class VerificationItem40(VerificationItem): - pass - - -class DeclaredBy41(DeclaredBy): - pass - - -class VerifyAgent82(VerifyAgent): - pass - - -class VerifyAgent83(VerifyAgent1): - pass - - -class VerificationItem41(VerificationItem): - pass - - -class DeclaredBy42(DeclaredBy): - pass - - -class VerifyAgent84(VerifyAgent): - pass - - -class VerifyAgent85(VerifyAgent1): - pass - - -class VerificationItem42(VerificationItem): - pass - - -class DeclaredBy43(DeclaredBy): - pass - - -class VerifyAgent86(VerifyAgent): - pass - - -class VerifyAgent87(VerifyAgent1): - pass - - -class VerificationItem43(VerificationItem): - pass - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DeclaredBy44(DeclaredBy): - pass - - -class VerifyAgent88(VerifyAgent): - pass - - -class VerifyAgent89(VerifyAgent1): - pass - - -class VerificationItem44(VerificationItem): - pass - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Use(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ExcludedCountry(Country): - pass - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class Right(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[Use], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: Annotated[ - RightType | None, - Field( - description='Type of rights (talent, music, etc.). Helps identify constraints when a creative combines multiple rights types.', - title='Right Type', - ), - ] = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Type(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy45(DeclaredBy): - pass - - -class VerifyAgent90(VerifyAgent): - pass - - -class VerifyAgent91(VerifyAgent1): - pass - - -class VerificationItem45(VerificationItem): - pass - - - -class DeclaredBy46(DeclaredBy): - pass - - -class VerifyAgent92(VerifyAgent): - pass - - -class VerifyAgent93(VerifyAgent1): - pass - - -class VerificationItem46(VerificationItem): - pass - - -class DeclaredBy47(DeclaredBy): - pass - - -class VerifyAgent94(VerifyAgent): - pass - - -class VerifyAgent95(VerifyAgent1): - pass - - -class VerificationItem47(VerificationItem): - pass - - -class DeclaredBy48(DeclaredBy): - pass - - -class VerifyAgent96(VerifyAgent): - pass - - -class VerifyAgent97(VerifyAgent1): - pass - - -class VerificationItem48(VerificationItem): - pass - - -class DeclaredBy49(DeclaredBy): - pass - - -class VerifyAgent98(VerifyAgent): - pass - - -class VerifyAgent99(VerifyAgent1): - pass - - -class VerificationItem49(VerificationItem): - pass - - -class DeclaredBy50(DeclaredBy): - pass - - -class VerifyAgent100(VerifyAgent): - pass - - -class VerifyAgent101(VerifyAgent1): - pass - - -class VerificationItem50(VerificationItem): - pass - - -class DeclaredBy51(DeclaredBy): - pass - - -class VerifyAgent102(VerifyAgent): - pass - - -class VerifyAgent103(VerifyAgent1): - pass - - -class VerificationItem51(VerificationItem): - pass - - -class DeclaredBy52(DeclaredBy): - pass - - -class VerifyAgent104(VerifyAgent): - pass - - -class VerifyAgent105(VerifyAgent1): - pass - - -class VerificationItem52(VerificationItem): - pass - - -class DeclaredBy53(DeclaredBy): - pass - - -class VerifyAgent106(VerifyAgent): - pass - - -class VerifyAgent107(VerifyAgent1): - pass - - -class VerificationItem53(VerificationItem): - pass - - -class DeclaredBy54(DeclaredBy): - pass - - -class VerifyAgent108(VerifyAgent): - pass - - -class VerifyAgent109(VerifyAgent1): - pass - - -class VerificationItem54(VerificationItem): - pass - - -class DeclaredBy55(DeclaredBy): - pass - - -class VerifyAgent110(VerifyAgent): - pass - - -class VerifyAgent111(VerifyAgent1): - pass - - -class VerificationItem55(VerificationItem): - pass - - -class DeclaredBy56(DeclaredBy): - pass - - -class VerifyAgent112(VerifyAgent): - pass - - -class VerifyAgent113(VerifyAgent1): - pass - - -class VerificationItem56(VerificationItem): - pass - - -class DeclaredBy57(DeclaredBy): - pass - - -class VerifyAgent114(VerifyAgent): - pass - - -class VerifyAgent115(VerifyAgent1): - pass - - -class VerificationItem57(VerificationItem): - pass - - -class DeclaredBy58(DeclaredBy): - pass - - -class VerifyAgent116(VerifyAgent): - pass - - -class VerifyAgent117(VerifyAgent1): - pass - - -class VerificationItem58(VerificationItem): - pass - - -class DeclaredBy59(DeclaredBy): - pass - - -class VerifyAgent118(VerifyAgent): - pass - - -class VerifyAgent119(VerifyAgent1): - pass - - -class VerificationItem59(VerificationItem): - pass - - -class ReferenceAsset2(ReferenceAsset): - pass - - -class FeedFieldMapping2(FeedFieldMapping): - pass - - -class ReferenceAuthorization2(ReferenceAuthorization): - pass - - -class DeclaredBy60(DeclaredBy): - pass - - -class VerifyAgent120(VerifyAgent): - pass - - -class VerifyAgent121(VerifyAgent1): - pass - - -class VerificationItem60(VerificationItem): - pass - - -class DeclaredBy61(DeclaredBy): - pass - - -class VerifyAgent122(VerifyAgent): - pass - - -class VerifyAgent123(VerifyAgent1): - pass - - -class VerificationItem61(VerificationItem): - pass - - -class DeclaredBy62(DeclaredBy): - pass - - -class VerifyAgent124(VerifyAgent): - pass - - -class VerifyAgent125(VerifyAgent1): - pass - - -class VerificationItem62(VerificationItem): - pass - - -class DeclaredBy63(DeclaredBy): - pass - - -class VerifyAgent126(VerifyAgent): - pass - - -class VerifyAgent127(VerifyAgent1): - pass - - -class VerificationItem63(VerificationItem): - pass - - -class DeclaredBy64(DeclaredBy): - pass - - -class VerifyAgent128(VerifyAgent): - pass - - -class VerifyAgent129(VerifyAgent1): - pass - - -class VerificationItem64(VerificationItem): - pass - - -class DeclaredBy65(DeclaredBy): - pass - - -class VerifyAgent130(VerifyAgent): - pass - - -class VerifyAgent131(VerifyAgent1): - pass - - -class VerificationItem65(VerificationItem): - pass - - -class DeclaredBy66(DeclaredBy): - pass - - -class VerifyAgent132(VerifyAgent): - pass - - -class VerifyAgent133(VerifyAgent1): - pass - - -class VerificationItem66(VerificationItem): - pass - - -class DeclaredBy67(DeclaredBy): - pass - - -class VerifyAgent134(VerifyAgent): - pass - - -class VerifyAgent135(VerifyAgent1): - pass - - -class VerificationItem67(VerificationItem): - pass - - -class DeclaredBy68(DeclaredBy): - pass - - -class VerifyAgent136(VerifyAgent): - pass - - -class VerifyAgent137(VerifyAgent1): - pass - - -class VerificationItem68(VerificationItem): - pass - - -class DeclaredBy69(DeclaredBy): - pass - - -class VerifyAgent138(VerifyAgent): - pass - - -class VerifyAgent139(VerifyAgent1): - pass - - -class VerificationItem69(VerificationItem): - pass - - -class DeclaredBy70(DeclaredBy): - pass - - -class VerifyAgent140(VerifyAgent): - pass - - -class VerifyAgent141(VerifyAgent1): - pass - - -class VerificationItem70(VerificationItem): - pass - - -class DeclaredBy71(DeclaredBy): - pass - - -class VerifyAgent142(VerifyAgent): - pass - - -class VerifyAgent143(VerifyAgent1): - pass - - -class VerificationItem71(VerificationItem): - pass - - -class DeclaredBy72(DeclaredBy): - pass - - -class VerifyAgent144(VerifyAgent): - pass - - -class VerifyAgent145(VerifyAgent1): - pass - - -class VerificationItem72(VerificationItem): - pass - - -class DeclaredBy73(DeclaredBy): - pass - - -class VerifyAgent146(VerifyAgent): - pass - - -class VerifyAgent147(VerifyAgent1): - pass - - -class VerificationItem73(VerificationItem): - pass - - -class DeclaredBy74(DeclaredBy): - pass - - -class VerifyAgent148(VerifyAgent): - pass - - -class VerifyAgent149(VerifyAgent1): - pass - - -class VerificationItem74(VerificationItem): - pass - - -class DeclaredBy75(DeclaredBy): - pass - - -class VerifyAgent150(VerifyAgent): - pass - - -class VerifyAgent151(VerifyAgent1): - pass - - -class VerificationItem75(VerificationItem): - pass - - -class DeclaredBy76(DeclaredBy): - pass - - -class VerifyAgent152(VerifyAgent): - pass - - -class VerifyAgent153(VerifyAgent1): - pass - - -class VerificationItem76(VerificationItem): - pass - - -class DeclaredBy77(DeclaredBy): - pass - - -class VerifyAgent154(VerifyAgent): - pass - - -class VerifyAgent155(VerifyAgent1): - pass - - -class VerificationItem77(VerificationItem): - pass - - -class DeclaredBy78(DeclaredBy): - pass - - -class VerifyAgent156(VerifyAgent): - pass - - -class VerifyAgent157(VerifyAgent1): - pass - - -class VerificationItem78(VerificationItem): - pass - - -class DeclaredBy79(DeclaredBy): - pass - - -class VerifyAgent158(VerifyAgent): - pass - - -class VerifyAgent159(VerifyAgent1): - pass - - -class VerificationItem79(VerificationItem): - pass - - -class DeclaredBy80(DeclaredBy): - pass - - -class VerifyAgent160(VerifyAgent): - pass - - -class VerifyAgent161(VerifyAgent1): - pass - - -class VerificationItem80(VerificationItem): - pass - - -class DeclaredBy81(DeclaredBy): - pass - - -class VerifyAgent162(VerifyAgent): - pass - - -class VerifyAgent163(VerifyAgent1): - pass - - -class VerificationItem81(VerificationItem): - pass - - -class ReferenceAsset3(ReferenceAsset): - pass - - -class FeedFieldMapping3(FeedFieldMapping): - pass - - -class ReferenceAuthorization3(ReferenceAuthorization): - pass - - -class DeclaredBy82(DeclaredBy): - pass - - -class VerifyAgent164(VerifyAgent): - pass - - -class VerifyAgent165(VerifyAgent1): - pass - - -class VerificationItem82(VerificationItem): - pass - - -class DeclaredBy83(DeclaredBy): - pass - - -class VerifyAgent166(VerifyAgent): - pass - - -class VerifyAgent167(VerifyAgent1): - pass - - -class VerificationItem83(VerificationItem): - pass - - -class DeclaredBy84(DeclaredBy): - pass - - -class VerifyAgent168(VerifyAgent): - pass - - -class VerifyAgent169(VerifyAgent1): - pass - - -class VerificationItem84(VerificationItem): - pass - - -class DeclaredBy85(DeclaredBy): - pass - - -class VerifyAgent170(VerifyAgent): - pass - - -class VerifyAgent171(VerifyAgent1): - pass - - -class VerificationItem85(VerificationItem): - pass - - -class DeclaredBy86(DeclaredBy): - pass - - -class VerifyAgent172(VerifyAgent): - pass - - -class VerifyAgent173(VerifyAgent1): - pass - - -class VerificationItem86(VerificationItem): - pass - - -class DeclaredBy87(DeclaredBy): - pass - - -class VerifyAgent174(VerifyAgent): - pass - - -class VerifyAgent175(VerifyAgent1): - pass - - -class VerificationItem87(VerificationItem): - pass - - -class DeclaredBy88(DeclaredBy): - pass - - -class VerifyAgent176(VerifyAgent): - pass - - -class VerifyAgent177(VerifyAgent1): - pass - - -class VerificationItem88(VerificationItem): - pass - - -class DeclaredBy89(DeclaredBy): - pass - - -class VerifyAgent178(VerifyAgent): - pass - - -class VerifyAgent179(VerifyAgent1): - pass - - -class VerificationItem89(VerificationItem): - pass - - -class DeclaredBy90(DeclaredBy): - pass - - -class VerifyAgent180(VerifyAgent): - pass - - -class VerifyAgent181(VerifyAgent1): - pass - - -class VerificationItem90(VerificationItem): - pass - - -class Right1(Right): - pass - - -class IndustryIdentifier1(IndustryIdentifier): - pass - - -class DeclaredBy91(DeclaredBy): - pass - - -class VerifyAgent182(VerifyAgent): - pass - - -class VerifyAgent183(VerifyAgent1): - pass - - -class VerificationItem91(VerificationItem): - pass - - -class TargetFormatId(FormatId): - pass - - -class Mode(StrEnum): - execute = 'execute' - estimate = 'estimate' - - -class MaxSpend(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - amount: Annotated[ - float, - Field( - description='Maximum aggregate vendor_cost to incur on this call, in `currency`.', - ge=0.0, - ), - ] - currency: Annotated[ - str, Field(description='ISO 4217 currency; MUST match the rate card.', pattern='^[A-Z]{3}$') - ] - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalCondition1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalCondition2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalCondition3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalCondition4(AdCPBaseModel): - signal_agent_segment_id: Annotated[ - str | None, - Field( - description="Optional opaque resolved-segment handle for this fan-out condition — the RESOLVED condition identity, distinct from signal_ref's DEFINITION identity. When get_signals or a product signal_targeting_options entry exposed a signal_agent_segment_id for the targeted signal, echo it here verbatim so the produced creative's signal_condition carries the resolved-segment identity that the trafficking-compatibility check (SIGNAL_TARGETING_INCOMPATIBLE) matches on exactly. Providers MAY namespace it (e.g. provider_a:weather:rain_realtime vs provider_b:precip:high) so cross-provider conditions stay distinct without a shared taxonomy registry; treat as opaque, do not parse the namespace for business logic. Prefer it over reconstructing condition identity from categorical values — categorical {signal_ref,value} identity is the weaker fallback, appropriate only for inherently-categorical signals with no resolved handle, and never a cross-provider equivalence claim." - ), - ] = None - - -class SignalCondition5(SignalCondition1, SignalCondition4): - pass - - -class SignalCondition6(SignalCondition2, SignalCondition4): - pass - - -class SignalCondition7(SignalCondition3, SignalCondition4): - pass - - -class SignalCondition(RootModel[SignalCondition5 | SignalCondition6 | SignalCondition7]): - root: SignalCondition5 | SignalCondition6 | SignalCondition7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimension(StrEnum): - voice = 'voice' - theme = 'theme' - best_of_n = 'best_of_n' - transformer_config = 'transformer_config' - custom = 'custom' - - -class VariantAxis(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - dimension: Annotated[ - Dimension, - Field( - description='What varies across variants. `transformer_config` varies a named config param (name it in `field`); `best_of_n` lets the agent explore and rank; `custom` is agent-defined (describe in `label`).' - ), - ] - field: Annotated[ - str | None, - Field( - description='The transformer `config` param `field` this axis sweeps. REQUIRED when `dimension` is `transformer_config`; `values[]` are then interpreted as values for `config[field]`. Omit for other dimensions.' - ), - ] = None - values: Annotated[ - list[Any] | None, - Field( - description='Caller-fixed set of values for the axis (e.g. ["isaac", "sara"] for dimension `voice`). When present, len(values) is the variant count and is authoritative over max_variants.', - min_length=1, - ), - ] = None - label: Annotated[ - str | None, - Field(description='Human-readable description of the axis, especially for `custom`.'), - ] = None - - -class KeepMode(StrEnum): - keep_all = 'keep_all' - keep_one = 'keep_one' - keep_some = 'keep_some' - - -class SelectionStrategy(StrEnum): - audience_relevance = 'audience_relevance' - contextual_fit = 'contextual_fit' - performance = 'performance' - proximity = 'proximity' - inventory_priority = 'inventory_priority' - random = 'random' - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DeclaredBy92(DeclaredBy): - pass - - -class VerifyAgent184(VerifyAgent): - pass - - -class VerifyAgent185(VerifyAgent1): - pass - - -class VerificationItem92(VerificationItem): - pass - - -class DeclaredBy93(DeclaredBy): - pass - - -class VerifyAgent186(VerifyAgent): - pass - - -class VerifyAgent187(VerifyAgent1): - pass - - -class VerificationItem93(VerificationItem): - pass - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirementItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class Direction(StrEnum): - maximize = 'maximize' - minimize = 'minimize' - - -class RankByItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description='Creative feature to order by (discovered via get_adcp_capabilities; the same feature_id space the chosen evaluator form returns in eval.features[]).' - ), - ] - direction: Annotated[ - Direction | None, - Field( - description='Sort direction for this feature: `maximize` ranks higher feature values first (e.g. creative_quality_score), `minimize` ranks lower values first (e.g. predicted_cpa).' - ), - ] = Direction.maximize - - -class FeatureAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the get_creative_features-capable agent the producing agent calls to obtain creative-feature values for the gate. MUST use https:// and MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url`; off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical feature_id the producing agent SHOULD request against this agent. When present it SHOULD match the agent's `accepted_verifiers[].feature_id` or be omitted; when absent the seller selects a feature at evaluation time. Resolves selector ambiguity exactly as the provenance verify_agent.feature_id does." - ), - ] = None - - -class EvalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_calls: Annotated[ - int | None, - Field(description='Soft cap on the number of judge calls the evaluator should make.', ge=1), - ] = None - max_seconds: Annotated[ - float | None, - Field(description='Soft cap on wall-clock seconds the evaluation should consume.', ge=0.0), - ] = None - - -class Role98(StrEnum): - title = 'title' # type: ignore[assignment] - paragraph = 'paragraph' - heading = 'heading' - caption = 'caption' - quote = 'quote' - list_item = 'list_item' - description = 'description' - - -class ContentFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - text_html = 'text/html' - application_json = 'application/json' - - -class DeclaredBy94(DeclaredBy): - pass - - -class VerifyAgent188(VerifyAgent): - pass - - -class VerifyAgent189(VerifyAgent1): - pass - - -class VerificationItem94(VerificationItem): - pass - - -class DeclaredBy95(DeclaredBy): - pass - - -class VerifyAgent190(VerifyAgent): - pass - - -class VerifyAgent191(VerifyAgent1): - pass - - -class VerificationItem95(VerificationItem): - pass - - -class TranscriptFormat(StrEnum): - text_plain = 'text/plain' - text_markdown = 'text/markdown' - application_json = 'application/json' - - -class TranscriptSource(StrEnum): - original_script = 'original_script' - subtitles = 'subtitles' - closed_captions = 'closed_captions' - dub = 'dub' - generated = 'generated' - - -class DeclaredBy96(DeclaredBy): - pass - - -class VerifyAgent192(VerifyAgent): - pass - - -class VerifyAgent193(VerifyAgent1): - pass - - -class VerificationItem96(VerificationItem): - pass - - -class TranscriptSource1(StrEnum): - original_script = 'original_script' - closed_captions = 'closed_captions' - generated = 'generated' - - -class DeclaredBy97(DeclaredBy): - pass - - -class VerifyAgent194(VerifyAgent): - pass - - -class VerifyAgent195(VerifyAgent1): - pass - - -class VerificationItem97(VerificationItem): - pass - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - canonical: Annotated[AnyUrl | None, Field(description='Canonical URL')] = None - author: Annotated[str | None, Field(description='Artifact author name')] = None - keywords: Annotated[str | None, Field(description='Artifact keywords')] = None - open_graph: Annotated[ - dict[str, Any] | None, Field(description='Open Graph protocol metadata') - ] = None - twitter_card: Annotated[dict[str, Any] | None, Field(description='Twitter Card metadata')] = ( - None - ) - json_ld: Annotated[ - list[dict[str, Any]] | None, Field(description='JSON-LD structured data (schema.org)') - ] = None - - -class DeclaredBy98(DeclaredBy): - pass - - -class VerifyAgent196(VerifyAgent): - pass - - -class VerifyAgent197(VerifyAgent1): - pass - - -class VerificationItem98(VerificationItem): - pass - - -class Identifiers(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - apple_podcast_id: Annotated[str | None, Field(description='Apple Podcasts ID')] = None - spotify_collection_id: Annotated[str | None, Field(description='Spotify collection ID')] = None - podcast_guid: Annotated[str | None, Field(description='Podcast GUID (from RSS feed)')] = None - youtube_video_id: Annotated[str | None, Field(description='YouTube video ID')] = None - rss_url: Annotated[AnyUrl | None, Field(description='RSS feed URL')] = None - - -class DeclaredBy99(DeclaredBy): - pass - - -class VerifyAgent198(VerifyAgent): - pass - - -class VerifyAgent199(VerifyAgent1): - pass - - -class VerificationItem99(VerificationItem): - pass - - -class DeclaredBy100(DeclaredBy): - pass - - -class VerifyAgent200(VerifyAgent): - pass - - -class VerifyAgent201(VerifyAgent1): - pass - - -class VerificationItem100(VerificationItem): - pass - - -class DeclaredBy101(DeclaredBy): - pass - - -class VerifyAgent202(VerifyAgent): - pass - - -class VerifyAgent203(VerifyAgent1): - pass - - -class VerificationItem101(VerificationItem): - pass - - -class DeclaredBy102(DeclaredBy): - pass - - -class VerifyAgent204(VerifyAgent): - pass - - -class VerifyAgent205(VerifyAgent1): - pass - - -class VerificationItem102(VerificationItem): - pass - - -class DeclaredBy103(DeclaredBy): - pass - - -class VerifyAgent206(VerifyAgent): - pass - - -class VerifyAgent207(VerifyAgent1): - pass - - -class VerificationItem103(VerificationItem): - pass - - -class FeatureRequirementItem1(FeatureRequirementItem): - pass - - -class RankByItem1(RankByItem): - pass - - -class Evaluator1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[FeatureRequirementItem1] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem1] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - evaluator_id: Annotated[ - str, - Field( - description='Account-scoped house evaluator preset selected by the buyer. This id is pre-provisioned/account-arranged, not discovered from get_adcp_capabilities governance.creative_features. That catalog only discovers the feature vocabulary the preset emits. An unknown id degrades to seller-default ranking (advisory errors[] note), not a failure.' - ), - ] - - -class FeatureRequirementItem2(FeatureRequirementItem): - pass - - -class RankByItem2(RankByItem): - pass - - -class Evaluator2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[FeatureRequirementItem2] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem2] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of an external get_creative_features-capable judge agent the seller calls to score the produced leaves. MUST match an entry in the seller's `creative_policy.accepted_verifiers[].agent_url` (off-list → `EVALUATOR_AGENT_NOT_ACCEPTED`); an on-list agent that is unreachable or rejects the producing agent's transport authentication degrades to seller-default ranking (advisory errors[] note), not a failure. Authentication and trust material for this call belongs on the transport or in account provisioning, not in the evaluator payload." - ), - ] - - -class PreviewInput(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Human-readable name for this input set (e.g., 'Sunny morning on mobile', 'Evening podcast ad')" - ), - ] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to use for this preview variant') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class PreviewOutputFormat(StrEnum): - url = 'url' - html = 'html' - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class AssetAccess1(AdCPBaseModel): - method: Literal['bearer_token'] = 'bearer_token' - token: Annotated[str, Field(description='OAuth2 bearer token for Authorization header')] - - -class Provider(StrEnum): - gcp = 'gcp' - aws = 'aws' - - -class AssetAccess2(AdCPBaseModel): - method: Literal['service_account'] = 'service_account' - provider: Annotated[Provider, Field(description='Cloud provider')] - credentials: Annotated[ - dict[str, Any] | None, Field(description='Service account credentials') - ] = None - - -class AssetAccess3(AdCPBaseModel): - method: Literal['signed_url'] = 'signed_url' - - -class AssetAccess(RootModel[AssetAccess1 | AssetAccess2 | AssetAccess3]): - root: Annotated[ - AssetAccess1 | AssetAccess2 | AssetAccess3, - Field( - description='Authentication for accessing secured asset URLs', discriminator='method' - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class CreativeQuality(StrEnum): - draft = 'draft' - production = 'production' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets15(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets16(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction16(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction18(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media1, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target1 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target1.linear - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction26(Jurisdiction): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction26] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent52 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent53 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction27(Jurisdiction): - pass - - -class Disclosure26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction27] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy26 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem26] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark26] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure26 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem26] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance26 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent54 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent55 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction28(Jurisdiction): - pass - - -class Disclosure27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction28] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy27 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem27] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark27] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure27 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem27] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance27 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent56 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent57 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction29(Jurisdiction): - pass - - -class Disclosure28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction29] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy28 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem28] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark28] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure28 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem28] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance28 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent58 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent59 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction30(Jurisdiction): - pass - - -class Disclosure29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction30] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy29 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem29] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark29] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure29 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem29] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance29 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent60 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent61 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction31(Jurisdiction): - pass - - -class Disclosure30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction31] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy30 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem30] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark30] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure30 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem30] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance30 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent62 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent63 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction32(Jurisdiction): - pass - - -class Disclosure31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction32] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy31 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem31] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark31] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure31 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem31] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance31 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent64 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent65 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction33(Jurisdiction): - pass - - -class Disclosure32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction33] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy32 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem32] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark32] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure32 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem32] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance32 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent66 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent67 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction34(Jurisdiction): - pass - - -class Disclosure33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction34] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy33 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem33] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark33] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure33 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem33] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance33 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent68 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent69 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction35(Jurisdiction): - pass - - -class Disclosure34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction35] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy34 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem34] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark34] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure34 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem34] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance34 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent70 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent71 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction36(Jurisdiction): - pass - - -class Disclosure35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction36] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy35 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem35] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark35] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure35 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem35] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance35 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure1(RequiredDisclosure): - pass - - -class Compliance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure1] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets2217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset1] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance1 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets2218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping1] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent72 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent73 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction38(Jurisdiction): - pass - - -class Disclosure36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction38] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy36 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem36] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark36] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure36 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem36] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance36 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent74 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent75 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction39(Jurisdiction): - pass - - -class Disclosure37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction39] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy37 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem37] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark37] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure37 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem37] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance37 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent76 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent77 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction40(Jurisdiction): - pass - - -class Disclosure38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction40] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy38 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem38] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark38] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure38 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem38] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance38 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent78 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent79 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction41(Jurisdiction): - pass - - -class Disclosure39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction41] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy39 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem39] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark39] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure39 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem39] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance39 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent80 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent81 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction42(Jurisdiction): - pass - - -class Disclosure40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction42] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy40 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem40] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark40] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure40 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem40] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media2 | Media3, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl1 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance40 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent82 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent83 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction43(Jurisdiction): - pass - - -class Disclosure41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction43] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy41 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem41] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark41] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure41 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem41] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance41 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent84 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent85 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction44(Jurisdiction): - pass - - -class Disclosure42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction44] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy42 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem42] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark42] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure42 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem42] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance42 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent86 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent87 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction45(Jurisdiction): - pass - - -class Disclosure43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction45] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy43 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem43] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark43] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure43 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem43] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target1 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target1.linear - provenance: Annotated[ - Provenance43 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets221( - RootModel[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223 - ] -): - root: Annotated[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets22(RootModel[list[Assets221]]): - root: Annotated[list[Assets221], Field(min_length=1)] - - -class EmbeddedProvenanceItem44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent88 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent89 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction46(Jurisdiction): - pass - - -class Disclosure44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction46] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy44 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem44] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark44] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure44 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem44] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance44 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent90 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent91 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction47(Jurisdiction): - pass - - -class Disclosure45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction47] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy45 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem45] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark45] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure45 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem45] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: Annotated[ - FormatKind | None, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets1 - | Assets2 - | Assets3 - | Assets4 - | Assets5 - | Assets6 - | Assets7 - | Assets8 - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - | Assets16 - | Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance45 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent92 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent93 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction48(Jurisdiction): - pass - - -class Disclosure46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction48] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy46 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem46] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark46] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure46 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem46] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance46 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent94 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent95 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction49(Jurisdiction): - pass - - -class Disclosure47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction49] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy47 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem47] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark47] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure47 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem47] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance47 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent96 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent97 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction50(Jurisdiction): - pass - - -class Disclosure48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction50] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy48 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem48] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark48] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure48 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem48] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance48 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent98 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent99 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction51(Jurisdiction): - pass - - -class Disclosure49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction51] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy49 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem49] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark49] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure49 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem49] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance49 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction52(Jurisdiction): - pass - - -class Disclosure50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction52] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy50 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem50] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark50] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure50 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem50] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance50 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction53(Jurisdiction): - pass - - -class Disclosure51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction53] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy51 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem51] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark51] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure51 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem51] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance51 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction54(Jurisdiction): - pass - - -class Disclosure52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction54] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy52 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem52] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark52] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure52 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem52] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance52 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction55(Jurisdiction): - pass - - -class Disclosure53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction55] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy53 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem53] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark53] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure53 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem53] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance53 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction56(Jurisdiction): - pass - - -class Disclosure54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction56] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy54 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem54] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark54] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure54 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem54] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance54 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction57(Jurisdiction): - pass - - -class Disclosure55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction57] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy55 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem55] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark55] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure55 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem55] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance55 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction58(Jurisdiction): - pass - - -class Disclosure56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction58] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy56 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem56] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark56] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure56 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem56] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance56 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction59(Jurisdiction): - pass - - -class Disclosure57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction59] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy57 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem57] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark57] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure57 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem57] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance57 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction60(Jurisdiction): - pass - - -class Disclosure58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction60] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy58 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem58] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark58] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure58 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem58] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance58 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction61(Jurisdiction): - pass - - -class Disclosure59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction61] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy59 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem59] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark59] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure59 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem59] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance59 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets37(Assets14): - pass - - -class RequiredDisclosure2(RequiredDisclosure): - pass - - -class Compliance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure2] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets38(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset2] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance2 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets39(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping2] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction63(Jurisdiction): - pass - - -class Disclosure60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction63] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy60 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem60] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark60] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure60 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem60] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization2 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance60 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction64(Jurisdiction): - pass - - -class Disclosure61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction64] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy61 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem61] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark61] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure61 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem61] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance61 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction65(Jurisdiction): - pass - - -class Disclosure62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction65] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy62 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem62] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark62] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure62 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem62] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance62 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction66(Jurisdiction): - pass - - -class Disclosure63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction66] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy63 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem63] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark63] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure63 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem63] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance63 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction67(Jurisdiction): - pass - - -class Disclosure64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction67] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy64 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem64] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark64] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure64 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem64] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media4 | Media5, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl2 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance64 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction68(Jurisdiction): - pass - - -class Disclosure65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction68] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy65 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem65] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark65] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure65 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem65] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance65 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction69(Jurisdiction): - pass - - -class Disclosure66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction69] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy66 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem66] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark66] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure66 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem66] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance66 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction70(Jurisdiction): - pass - - -class Disclosure67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction70] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy67 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem67] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark67] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure67 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem67] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target1 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target1.linear - provenance: Annotated[ - Provenance67 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction71(Jurisdiction): - pass - - -class Disclosure68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction71] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy68 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem68] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark68] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure68 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem68] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance68 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction72(Jurisdiction): - pass - - -class Disclosure69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction72] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy69 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem69] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark69] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure69 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem69] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance69 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction73(Jurisdiction): - pass - - -class Disclosure70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction73] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy70 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem70] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark70] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure70 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem70] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance70 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction74(Jurisdiction): - pass - - -class Disclosure71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction74] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy71 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem71] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark71] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure71 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem71] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance71 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction75(Jurisdiction): - pass - - -class Disclosure72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction75] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy72 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem72] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark72] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure72 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem72] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance72 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction76(Jurisdiction): - pass - - -class Disclosure73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction76] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy73 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem73] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark73] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure73 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem73] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance73 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction77(Jurisdiction): - pass - - -class Disclosure74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction77] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy74 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem74] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark74] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure74 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem74] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance74 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction78(Jurisdiction): - pass - - -class Disclosure75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction78] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy75 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem75] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark75] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure75 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem75] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance75 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction79(Jurisdiction): - pass - - -class Disclosure76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction79] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy76 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem76] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark76] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure76 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem76] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance76 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction80(Jurisdiction): - pass - - -class Disclosure77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction80] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy77 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem77] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark77] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure77 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem77] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance77 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction81(Jurisdiction): - pass - - -class Disclosure78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction81] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy78 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem78] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark78] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure78 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem78] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance78 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction82(Jurisdiction): - pass - - -class Disclosure79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction82] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy79 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem79] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark79] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure79 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem79] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance79 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction83(Jurisdiction): - pass - - -class Disclosure80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction83] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy80 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem80] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark80] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure80 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem80] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance80 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction84(Jurisdiction): - pass - - -class Disclosure81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction84] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy81 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem81] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark81] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure81 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem81] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance81 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure3(RequiredDisclosure): - pass - - -class Compliance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure3] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets4517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset3] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance3 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets4518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping3] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction86(Jurisdiction): - pass - - -class Disclosure82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction86] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy82 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem82] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark82] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure82 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem82] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization3 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance82 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction87(Jurisdiction): - pass - - -class Disclosure83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction87] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy83 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem83] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark83] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure83 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem83] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance83 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction88(Jurisdiction): - pass - - -class Disclosure84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction88] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy84 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem84] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark84] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure84 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem84] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance84 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction89(Jurisdiction): - pass - - -class Disclosure85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction89] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy85 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem85] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark85] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure85 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem85] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance85 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction90(Jurisdiction): - pass - - -class Disclosure86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction90] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy86 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem86] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark86] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure86 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem86] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media6 | Media7, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl3 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance86 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction91(Jurisdiction): - pass - - -class Disclosure87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction91] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy87 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem87] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark87] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure87 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem87] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance87 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction92(Jurisdiction): - pass - - -class Disclosure88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction92] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy88 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem88] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark88] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure88 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem88] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target.linear - provenance: Annotated[ - Provenance88 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction93(Jurisdiction): - pass - - -class Disclosure89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction93] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy89 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem89] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark89] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure89 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem89] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target1 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target1.linear - provenance: Annotated[ - Provenance89 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets451( - RootModel[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523 - ] -): - root: Annotated[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets45(RootModel[list[Assets451]]): - root: Annotated[list[Assets451], Field(min_length=1)] - - -class EmbeddedProvenanceItem90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction94(Jurisdiction): - pass - - -class Disclosure90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction94] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy90 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem90] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark90] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure90 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem90] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance90 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction95(Jurisdiction): - pass - - -class Disclosure91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction95] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy91 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem91] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark91] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure91 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem91] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: Annotated[ - FormatKind, - Field( - description="3.1+ canonical-format path. The canonical format name this manifest targets (e.g., `image`, `video_hosted`, `audio_daast`, `sponsored_placement`). Selects which canonical the seller validates the manifest's assets against. Mutually exclusive with `format_id`.", - title='Canonical Format Kind', - ), - ] - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - | Assets30 - | Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand1 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right1] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier1] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance91 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction96(Jurisdiction): - pass - - -class Disclosure92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction96] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy92 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem92] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark92] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure92 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem92] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance92 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand2, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class EmbeddedProvenanceItem93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction97(Jurisdiction): - pass - - -class Disclosure93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction97] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy93 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem93] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark93] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure93 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem93] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance93 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction98(Jurisdiction): - pass - - -class Disclosure94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction98] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy94 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem94] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark94] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure94 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem94] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role98 | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance94 | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction99(Jurisdiction): - pass - - -class Disclosure95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction99] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy95 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem95] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark95] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure95 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem95] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets48(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance95 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent192 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent193 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction100(Jurisdiction): - pass - - -class Disclosure96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction100] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy96 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem96] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark96] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure96 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem96] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance96 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent194 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent195 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction101(Jurisdiction): - pass - - -class Disclosure97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction101] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy97 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem97] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark97] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure97 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem97] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets50(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource1 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance97 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets46(RootModel[Assets47 | Assets48 | Assets49 | Assets50]): - root: Annotated[Assets47 | Assets48 | Assets49 | Assets50, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent196 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent197 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction102(Jurisdiction): - pass - - -class Disclosure98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction102] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy98 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem98] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark98] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure98 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem98] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Pas(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets46], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance98 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class EmbeddedProvenanceItem99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent198 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent199 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction103(Jurisdiction): - pass - - -class Disclosure99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction103] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy99 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem99] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark99] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure99 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem99] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52(AdCPBaseModel): - type: Literal['text'] = 'text' - role: Annotated[ - Role98 | None, - Field( - description="Role of this text in the document. Use 'title' for the main artifact title, 'description' for summaries." - ), - ] = None - content: Annotated[ - str, - Field( - description='Text content. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=100000, - ), - ] - content_format: Annotated[ - ContentFormat | None, - Field( - description='MIME type indicating how to parse the content field. Default: text/plain.' - ), - ] = ContentFormat.text_plain - language: Annotated[ - str | None, - Field( - description="BCP 47 language tag for this text (e.g., 'en', 'es-MX'). Useful when artifact contains mixed-language content." - ), - ] = None - heading_level: Annotated[ - int | None, Field(description='Heading level (1-6), only for role=heading', ge=1, le=6) - ] = None - provenance: Annotated[ - Provenance99 | None, - Field( - description='Provenance for this text block, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent200 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent201 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction104(Jurisdiction): - pass - - -class Disclosure100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction104] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy100 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem100] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark100] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure100 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem100] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets53(AdCPBaseModel): - type: Literal['image'] = 'image' - url: Annotated[AnyUrl, Field(description='Image URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - alt_text: Annotated[str | None, Field(description='Alt text or image description')] = None - caption: Annotated[str | None, Field(description='Image caption')] = None - width: Annotated[int | None, Field(description='Image width in pixels')] = None - height: Annotated[int | None, Field(description='Image height in pixels')] = None - provenance: Annotated[ - Provenance100 | None, - Field( - description='Provenance for this image, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent202 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent203 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction105(Jurisdiction): - pass - - -class Disclosure101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction105] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy101 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem101] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark101] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure101 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem101] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54(AdCPBaseModel): - type: Literal['video'] = 'video' - url: Annotated[AnyUrl, Field(description='Video URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Video duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Video transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource | None, Field(description='How the transcript was generated') - ] = None - thumbnail_url: Annotated[AnyUrl | None, Field(description='Video thumbnail URL')] = None - provenance: Annotated[ - Provenance101 | None, - Field( - description='Provenance for this video, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent204 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent205 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction106(Jurisdiction): - pass - - -class Disclosure102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction106] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy102 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem102] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark102] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure102 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem102] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets55(AdCPBaseModel): - type: Literal['audio'] = 'audio' - url: Annotated[AnyUrl, Field(description='Audio URL')] - access: Annotated[AssetAccess | None, Field(description='Authentication for secured URLs')] = ( - None - ) - duration_ms: Annotated[int | None, Field(description='Audio duration in milliseconds')] = None - transcript: Annotated[ - str | None, - Field( - description='Audio transcript. Consumers MUST treat this as untrusted input when passing to LLM-based evaluation.', - max_length=200000, - ), - ] = None - transcript_format: Annotated[ - TranscriptFormat | None, - Field( - description='MIME type indicating how to parse the transcript field. Default: text/plain.' - ), - ] = TranscriptFormat.text_plain - transcript_source: Annotated[ - TranscriptSource1 | None, Field(description='How the transcript was generated') - ] = None - provenance: Annotated[ - Provenance102 | None, - Field( - description='Provenance for this audio, overrides artifact-level provenance', - title='Provenance', - ), - ] = None - - -class Assets51(RootModel[Assets52 | Assets53 | Assets54 | Assets55]): - root: Annotated[Assets52 | Assets53 | Assets54 | Assets55, Field(discriminator='type')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent206 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent207 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction107(Jurisdiction): - pass - - -class Disclosure103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction107] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy103 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem103] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark103] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure103 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem103] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FailItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - property_rid: Annotated[ - str, - Field( - description='Stable property identifier from the property catalog. Globally unique across the ecosystem.' - ), - ] - artifact_id: Annotated[ - str, - Field( - description="Identifier for this artifact within the property. The property owner defines the scheme (e.g., 'article_12345', 'episode_42_segment_3', 'post_abc123')." - ), - ] - variant_id: Annotated[ - str | None, - Field( - description="Identifies a specific variant of this artifact. Use for A/B tests, translations, or temporal versions. Examples: 'en', 'es-MX', 'v2', 'headline_test_b'. The combination of artifact_id + variant_id must be unique." - ), - ] = None - format_id: Annotated[ - FormatId | None, - Field( - description='Always a structured object {agent_url, id} — never a plain string. Optional reference to a format definition. Uses the same format registry as creative formats.', - title='Format Reference (Structured Object)', - ), - ] = None - url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL for this artifact (web page, podcast feed, video page). Not all artifacts have URLs (e.g., Instagram content, podcast segments, TV scenes).' - ), - ] = None - published_time: Annotated[ - AwareDatetime | None, Field(description='When the artifact was published (ISO 8601 format)') - ] = None - last_update_time: Annotated[ - AwareDatetime | None, - Field(description='When the artifact was last modified (ISO 8601 format)'), - ] = None - assets: Annotated[ - list[Assets51], - Field( - description='Artifact assets in document flow order - text blocks, images, video, audio', - max_length=200, - ), - ] - metadata: Annotated[ - Metadata | None, Field(description='Rich metadata extracted from the artifact') - ] = None - provenance: Annotated[ - Provenance103 | None, - Field( - description='Provenance metadata for this artifact. Serves as the default provenance for all assets within this artifact — individual assets can override with their own provenance.', - title='Provenance', - ), - ] = None - identifiers: Annotated[ - Identifiers | None, Field(description='Platform-specific identifiers for this artifact') - ] = None - - -class Exemplars(AdCPBaseModel): - pass_: Annotated[ - list[Pas] | None, - Field( - alias='pass', - description='Artifacts exemplifying variants the buyer considers good — the high end (≈1) of the calibrated prediction feature.', - ), - ] = None - fail: Annotated[ - list[FailItem] | None, - Field( - description='Artifacts exemplifying variants the buyer considers bad — the low end (≈0) of the calibrated prediction feature.' - ), - ] = None - - -class Evaluator(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feature_requirement: Annotated[ - list[FeatureRequirementItem] | None, - Field( - description='Optional hard GATE over creative-feature values — the predicates a leaf MUST satisfy for the producing agent to recommend/return it. Reuses the feature-requirement shape (min_value/max_value for quantitative features like creative_quality_score, allowed_values for binary/categorical) — the same predicate vocabulary that gates property/audience filters, which its own schema names as an intended creative-gate reuse. A leaf that fails any predicate is DROPPED from the agent\'s best_of_n survivors before ranking — this is internal pruning of which leaves the agent recommends, not an AdCP-layer block of an already-produced billable leaf (what is produced/billed is governed by max_variants/max_creatives/max_spend). Distinct from `rank_by`: the gate is a pass/fail predicate (drop on fail), `rank_by` is an ordering over survivors. Each predicate\'s `if_not_covered` (exclude|include, default exclude) is the fail-open knob when the source cannot measure that feature. A pass/warn/fail verdict is expressed as a categorical string feature value gated via `allowed_values` (e.g. ["pass"] or ["pass","warn"]) — the buyer\'s predicate decides whether warn passes; the verdict is derived, never stored on creative-feature-result. Omit to leave evaluation advisory (no leaf is dropped).', - min_length=1, - ), - ] = None - rank_by: Annotated[ - list[RankByItem] | None, - Field( - description='Optional RANK ordering over creative-feature values — an ordered list (most significant first) the agent uses to order the gate survivors into recommended/rank. An explicit {feature_id, direction} ordering rather than the feature-requirement predicate (which has no sort direction): the gate decides pass/fail, rank_by decides better/worse. Soft preference, never a gate: leaves are not dropped by rank_by, they are only ordered. Omit to let the evaluator/seller choose the ordering.', - min_length=1, - ), - ] = None - feature_agent: Annotated[ - FeatureAgent | None, - Field( - description="Optional buyer-attached pointer to a get_creative_features-capable creative-feature / governance agent the producing agent calls to evaluate each leaf (the gate's SOURCE of feature values). This is the buyer-represents → seller-calls pattern #5280 established for provenance, generalized to the evaluator gate: the buyer REPRESENTS which agent it used, but the seller is the verifier-of-record and decides which agent it actually calls. `agent_url` MUST appear (canonicalized per /docs/reference/url-canonicalization) in the seller's `creative_policy.accepted_verifiers[].agent_url`; an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED` (mirrors PROVENANCE_VERIFIER_NOT_ACCEPTED) before any outbound call. The outbound evaluator call authenticates on the transport; this pointer MUST NOT carry API keys, bearer tokens, client secrets, authorization values, JWKs, JWKS documents, or JWKS URIs. Reuses the same allowlist mechanism — no new allowlist is introduced. Distinct from the `agent_url` oneOf form, which names the evaluator's source directly; `feature_agent` attaches the gate's measurement source alongside any of the three forms." - ), - ] = None - eval_budget: Annotated[ - EvalBudget | None, - Field( - description='Optional soft ceiling on evaluation effort. Advisory in v1 with no billing coupling. Well-known soft fields max_calls / max_seconds; open for evaluator-specific knobs.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - exemplars: Annotated[ - Exemplars, - Field( - description='Pass/fail examples that calibrate the single prediction feature (e.g. `predicted_performance` in [0,1]) the evaluator returns in eval.features[]. Artifact-based, mirroring content-standards.json calibration_exemplars.' - ), - ] - - -class BuildCreativeRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - message: Annotated[ - str | None, - Field( - description='Natural language instructions for the transformation or generation. For pure generation, this is the creative brief. For transformation, this provides guidance on how to adapt the creative. For refinement, this describes the desired changes.' - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest | CreativeManifest1 | None, - Field( - description='Creative manifest to transform or generate from. For pure generation, this should include the target format_id and any required input assets. For transformation (e.g., resizing, reformatting), this is the complete creative to adapt. When creative_id is provided, the agent resolves the creative from its library and this field is ignored.', - title='Creative Manifest', - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description="Reference to a creative in the agent's library. The creative agent resolves this to a manifest from its library. Use this instead of creative_manifest when retrieving an existing creative for tag generation or format adaptation." - ), - ] = None - concept_id: Annotated[ - str | None, - Field( - description='Creative concept containing the creative. Creative agents SHOULD assign globally unique creative_id values; when they cannot guarantee uniqueness, concept_id is REQUIRED to disambiguate.' - ), - ] = None - media_buy_id: Annotated[ - str | None, - Field( - description='Media buy identifier for tag generation context. When the creative agent is also the ad server, this provides the trafficking context needed to generate placement-specific tags (e.g., CM360 placement ID). Not needed when tags are generated at the creative level (most creative platforms).' - ), - ] = None - package_id: Annotated[ - str | None, - Field( - description='Package identifier within the media buy. Used with media_buy_id when the creative agent needs line-item-level context for tag generation. Omit to get a tag not scoped to a specific package.' - ), - ] = None - target_format_id: Annotated[ - TargetFormatId | None, - Field( - description='Single format ID to generate. Mutually exclusive with target_format_ids. The format definition specifies required input assets and output structure.', - title='Format Reference (Structured Object)', - ), - ] = None - target_format_ids: Annotated[ - list[TargetFormatId] | None, - Field( - description='Array of format IDs to generate in a single call. Mutually exclusive with target_format_id. The creative agent produces one manifest per format. Each format definition specifies its own required input assets and output structure.', - min_length=1, - ), - ] = None - transformer_id: Annotated[ - str | None, - Field( - description="Selects an account-scoped transformer (discovered via list_transformers) to perform the build. One transformer per call. When present, the build uses this transformer and target_format_id/target_format_ids select which of its outputs to produce — they MUST be a subset of the transformer's output_format_ids. Render configuration goes in `config`." - ), - ] = None - config: Annotated[ - dict[str, Any] | None, - Field( - description='Typed render configuration for the selected transformer, keyed by each param\'s `field` (from the transformer\'s params[] in list_transformers). Example: { "voice": "isaac", "speaking_rate": 1.1, "mastering_preset": "podcast" }. The agent MUST validate `config` against the transformer\'s live params for this account and reject unrecognized keys and out-of-range / non-enumerated values with a field-attributed error (e.g. `config.voice`) rather than silently ignoring them — config drives a paid render. Genuinely vendor-specific or experimental knobs not declared as params belong in `ext`, not here. (The schema leaves this object open because legal keys are dynamic per transformer; strict validation is a normative agent obligation.) When `refine_from_build_variant_id` is set, `config` is applied as a DELTA over the parent leaf\'s config.' - ), - ] = None - refine_from_build_variant_id: Annotated[ - str | None, - Field( - description="Refine a previously produced variant: re-build from the referenced `build_variant_id`, applying the natural-language instruction in `message` and any `config` delta, and return NEW lineage-linked variant(s) — each with `parent_build_variant_id` set to this id. A refinement is never a mutation; the parent leaf is unchanged. The `transformer_id` and target format(s) are inherited from the parent and need not be repeated; passing a `transformer_id` or `target_format_id`/`target_format_ids` that differs from the parent's is rejected with `INVALID_REQUEST`. Composes with `max_variants` / `variant_axis` (produces N refined alternatives), but is mutually exclusive with `max_creatives` / catalog fan-out (you refine one prior creative, not a catalog). Requires the agent to advertise `creative.supports_refinement: true` in get_adcp_capabilities; agents that do not retain prior builds reject with `UNSUPPORTED_FEATURE`. A ref that is unknown or no longer retained (agents retain produced leaves for an agent-defined window) is rejected with `REFERENCE_NOT_FOUND`, with `error.field` set to `refine_from_build_variant_id`. To refine a buyer-held manifest when the agent retains nothing, use the transform path instead (`creative_manifest` + `message`)." - ), - ] = None - mode: Annotated[ - Mode | None, - Field( - description="`execute` (default) produces and bills the creative(s). `estimate` is a DRY RUN: the agent produces nothing and bills nothing, and returns a BuildCreativeEstimate with a projected cost band (cost_low/cost_high) computed against THIS request's actual inputs (script length, brief, catalog size, max_creatives × max_variants) — the band the buyer cannot derive itself, since per_unit gives the rate but not the unit count. Requires the agent to advertise `creative.supports_spend_controls`; otherwise rejected with `UNSUPPORTED_FEATURE`." - ), - ] = Mode.execute - max_spend: Annotated[ - MaxSpend | None, - Field( - description='Hard per-call spend ceiling. The agent produces leaves until the NEXT leaf would push the run\'s aggregate vendor_cost over `amount`, then STOPS and returns the partial BuildCreativeVariantSuccess produced so far with `budget_status: "capped"` (every returned leaf is real, trafficable, and billed — nothing produced is discarded; the leaf shortfall is `leaves_returned` < `leaves_total`). If even the first leaf would exceed the cap, the call fails with BUDGET_CAP_REACHED. `currency` MUST match the rate card\'s currency (the agent does not FX-convert) or the request is rejected with INVALID_REQUEST (error.field `max_spend.currency`). Requires `creative.supports_spend_controls`. Caps a SINGLE call — to bound a refinement loop, track aggregate vendor_cost across calls and stop issuing them (buyer responsibility in this revision). max_spend bounds only build-time vendor_cost: CPM-priced builds (estimate basis `cpm_deferred`) have build-time vendor_cost 0 and accrue at serve time, so max_spend never engages for them — bound a CPM fan-out with max_creatives instead.' - ), - ] = None - max_creatives: Annotated[ - int | None, - Field( - description='Caps how many DISTINCT creatives to produce along the catalog/item fan-out axis — one creative per catalog item. Use it to sample a large catalog (e.g. send 150 job openings, set max_creatives: 5 to preview five). Distinct from item_limit, which caps how many catalog items a SINGLE creative consumes (DCO-style). Omitted with a catalog input means one creative per item up to the catalog/format bound; omitted without a catalog collapses to a single creative. Large fan-outs may return asynchronously. Mutually exclusive with `refine_from_build_variant_id` (refinement targets one prior creative, not a catalog fan-out). Supported only when the agent advertises `creative.multiplicity.supports_catalog_fanout`; values above `max_creatives_limit` are clamped. Pair with `max_spend` to bound the bill of a large fan-out.', - ge=1, - ), - ] = None - signal_conditions: Annotated[ - list[SignalCondition] | None, - Field( - description="Advisory keep-all PRODUCTION axis: produce one distinct creative group per signal condition, each kept and trafficked with its own signal targeting (e.g. a rain creative AND a sun creative). Sibling to max_creatives (catalog axis), NOT a variant_axis value (which is choose-among). Each item reuses SignalTargeting (value_type-discriminated binary/categorical/numeric over signal_ref) so the produced group's signal_condition resolves condition identity through the SAME schema the sales-side package targeting uses, plus an optional signal_agent_segment_id carrying the RESOLVED-segment identity (vs signal_ref's definition identity) — echo a provider-exposed handle verbatim; it is the primary trafficking-compatibility key, with categorical signal_ref+value as the weaker fallback. Per #5280 this is an ADVISORY context pointer — it informs production and MUST NOT hard-block at the build_creative layer; trafficking-compatibility (a sun creative MUST NOT serve into rain-targeted packages) is enforced reject-at-trafficking on the sales side (SIGNAL_TARGETING_INCOMPATIBLE), not here. Triggers the BuildCreativeVariantSuccess shape. Supported only when the agent advertises creative.multiplicity.supports_signal_fanout; condition counts above max_signal_conditions_limit are CLAMPED (not rejected), consistent with max_creatives. Composes with max_creatives (catalog × conditions cross-product) and max_variants (variants per group).", - min_length=1, - ), - ] = None - max_variants: Annotated[ - int | None, - Field( - description='Caps how many ALTERNATIVES to produce per creative (different voices, themes, best-of-N, etc.). Default 1 preserves single-output behavior. Each variant is a real, independently-billed build (you pay for all produced); the buyer keeps one or many. When variant_axis.values[] is provided, its length is authoritative over max_variants. Resolutions/quality tiers are NOT variants — request them as additional target formats.', - ge=1, - ), - ] = 1 - variant_axis: Annotated[ - VariantAxis | None, - Field( - description='Declares the dimension along which variants differ. When `values` is provided, the agent produces exactly one variant per value (e.g. an A/B of two voices). When only `dimension` is provided, the agent chooses up to max_variants variants along that dimension (e.g. best-of-N, themes).' - ), - ] = None - keep_mode: Annotated[ - KeepMode | None, - Field( - description='Advisory hint for how the buyer intends to use the variants. `keep_one` (best-of-N) and `keep_some` signal the agent to set `recommended`/`rank` on returned variants. Advisory only — it does not change what is returned or billed; every produced variant is returned and charged. Keeping is a client act of trafficking the chosen build_variant_id(s).' - ), - ] = KeepMode.keep_all - selection_strategy: Annotated[ - SelectionStrategy | None, - Field( - description='Governs HOW the agent samples when max_creatives < items_total (folds #5262). audience_relevance draws its ranking input from the SAME signal_ref pointers in signal_conditions / package targeting — NOT a parallel signals[] array. proximity takes a location input (geo shape TBD — WG open). inventory_priority is seller-side catalog metadata (margin/overstock/promo; no buyer input). random is the status-quo default. Per-creative selection ordering surfaces on the existing rank / recommended fields of creatives[].variants[], not a new selection_rank. Advisory; absent => agent default (random).', - title='Creative Selection Strategy', - ), - ] = None - account: Annotated[ - Account | Account1 | None, - Field( - description='Account reference for pricing and billing. When present, the creative agent applies account-specific pricing from the rate card, records the build against the account for billing, and can enforce account-level quotas or entitlements. Required by creative agents that charge for their services.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - brand: Annotated[ - Brand3 | None, - Field( - description='Brand reference for creative generation. Resolved to full brand identity (colors, logos, tone) at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - quality: CreativeQuality | None = None - evaluator: Annotated[ - Evaluator | Evaluator1 | Evaluator2 | None, - Field( - description="Optional advisory evaluator (buyer-attached pointer, #5280) declaring how produced variants should be evaluated and ranked — the rank-side of the get_creative_features feature oracle. Experimental (x-status: experimental): the whole evaluator surface is new and unfrozen, and requires creative.supports_evaluator, which sellers MUST pair with `creative.evaluator` in experimental_features. Drives the producing agent's gate-then-rank pipeline over its best_of_n exploration: per leaf, evaluate (the chosen form) → optionally GATE (`evaluator.feature_requirement[]`, drop fails — internal pruning of which leaves the agent recommends, never an AdCP-layer block of an already-produced billable leaf) → RANK survivors (`evaluator.rank_by`, an explicit {feature_id, direction} ordering). Feature discovery uses get_adcp_capabilities governance.creative_features for rank_by, feature_requirement, and eval.features[]; evaluator_id is a pre-provisioned/account-arranged preset, not an ID discovered from that catalog. Populates a per-leaf `eval` block of creative-feature values (creative-feature-result[]) when supports_evaluator. When the evaluator names an external agent (`evaluator.feature_agent.agent_url` or the agent-form `agent_url`), that agent MUST appear in the seller's `creative_policy.accepted_verifiers[]` (the same allowlist #5280 established for provenance verify_agent); an off-list agent is rejected with `EVALUATOR_AGENT_NOT_ACCEPTED`. The outbound evaluator call authenticates on the transport (request signing/JWKS, mTLS, or a pre-provisioned static credential); credentials and caller-supplied trust material MUST NOT appear in evaluator, context, ext, or creative payload fields, and credential- or trust-material keys should be rejected with `CREDENTIAL_IN_ARGS`. With no `feature_requirement`, evaluation is advisory only and does not change what is produced or billed; an unreachable/unknown on-list agent degrades to seller-default ranking (advisory errors[] note), not a failure. Requires creative.supports_evaluator; otherwise ignored.", - title='Evaluator Spec', - ), - ] = None - item_limit: Annotated[ - int | None, - Field( - description="Maximum number of catalog items a SINGLE creative consumes when generating (DCO-style — e.g. how many items fill one carousel/feed creative). When a catalog asset contains more items than this limit, the creative agent selects the top items based on relevance or catalog ordering. When item_limit exceeds the format's max_items, the creative agent SHOULD use the lesser of the two. Ignored when the manifest contains no catalog assets. Distinct from `max_creatives`, which fans OUT across catalog items to produce one distinct creative per item.", - ge=1, - ), - ] = None - include_preview: Annotated[ - bool | None, - Field( - description="When true, requests the creative agent to include preview renders in the response alongside the manifest. Agents that support this return a 'preview' object in the response using the same structure as preview_creative. Agents that do not support inline preview simply omit the field. This avoids a separate preview_creative round trip for platforms that generate previews as a byproduct of building." - ), - ] = None - preview_inputs: Annotated[ - list[PreviewInput] | None, - Field( - description='Input sets for preview generation when include_preview is true. Each input set defines macros and context values for one preview variant. If include_preview is true but this is omitted, the agent generates a single default preview. Only supported with target_format_id (single-format requests). Ignored when using target_format_ids — multi-format requests generate one default preview per format. Ignored when include_preview is false or omitted.', - min_length=1, - ), - ] = None - preview_quality: CreativeQuality | None = None - preview_output_format: Annotated[ - PreviewOutputFormat | None, - Field( - description="Output format for preview renders when include_preview is true. 'url' returns preview_url (iframe-embeddable URL), 'html' returns preview_html (raw HTML). Ignored when include_preview is false or omitted.", - title='Preview Output Format', - ), - ] = PreviewOutputFormat.url - macro_values: Annotated[ - dict[str, str] | None, - Field( - description="Macro values to pre-substitute into the output manifest's assets. Keys are universal macro names (e.g., CLICK_URL, CACHEBUSTER); values are the substitution strings. The creative agent translates universal macros to its platform's native syntax. Substitution is literal — all occurrences of each macro in output assets are replaced with the provided value. The caller is responsible for URL-encoding values if the output context requires it. Macros not provided here remain as {MACRO} placeholders for the sales agent to resolve at serve time. Creative agents MUST ignore keys they do not recognize — unknown macro names are not an error." - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate creative generation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on build_creative. Meaningful only when the request enters the async lifecycle and returns a Submitted envelope. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change response timing semantics: agents MUST NOT route a request through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; requests that can be completed inline still return the synchronous success shape.', - title='Push Notification Config', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/build_creative_response.py b/src/adcp/types/generated_poc/bundled/media_buy/build_creative_response.py deleted file mode 100644 index e83feb39..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/build_creative_response.py +++ /dev/null @@ -1,695 +0,0 @@ -# generated by datamodel-codegen: -# filename: build_creative_response.json -# timestamp: 2026-06-18T11:28:17+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class BuildCreativeResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class RightUse(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' diff --git a/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_request.py b/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_request.py deleted file mode 100644 index 8c91a0f4..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_request.py +++ /dev/null @@ -1,25206 +0,0 @@ -# generated by datamodel-codegen: -# filename: create_media_buy_request.json -# timestamp: 2026-06-18T11:28:25+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class TotalBudget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - amount: Annotated[float, Field(description='Total budget amount', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code')] - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatOptionRefs1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRefs2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class FormatOptionRefs(RootModel[FormatOptionRefs1 | FormatOptionRefs2]): - root: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)] - - -class Target3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - float, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class PostClick(Window): - pass - - -class PostView(Window): - pass - - -class Model(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class AttributionWindow(AdCPBaseModel): - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: Annotated[ - Model | None, - Field( - description="Attribution model used to assign credit when multiple touchpoints exist. SHOULD be populated when committing to a specific model; when absent, the seller's default applies.", - title='Attribution Model', - ), - ] = None - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class Target5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class GeoCountry(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas4(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude1(GeoPostalAreas1): - pass - - -class GeoPostalAreasExclude2(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude3(GeoPostalAreas3): - pass - - -class GeoPostalAreasExclude4(GeoPostalAreas4): - pass - - -class GeoPostalAreasExclude5(GeoPostalAreas5): - pass - - -class GeoPostalAreasExclude6(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude7(GeoPostalAreas7): - pass - - -class GeoPostalAreasExclude8(GeoPostalAreas8): - pass - - -class GeoPostalAreasExclude9(GeoPostalAreas9): - pass - - -class GeoPostalAreasExclude10(GeoPostalAreas10): - pass - - -class Day(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[Day], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Signal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Signal4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal5(Signal1, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal6(Signal2, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal7(Signal3, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal5 | Signal6 | Signal7]): - root: Annotated[ - Signal5 | Signal6 | Signal7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting(RootModel[SignalTargeting1 | SignalTargeting2 | SignalTargeting3]): - root: Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress(Window): - pass - - -class Window1(Window): - pass - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class AcceptedMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AcceptedMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class DevicePlatformEnum(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Unit5(StrEnum): - min = 'min' - hr = 'hr' - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: Annotated[ - Unit5, - Field( - description='Time unit for isochrone (travel-time catchment) calculations.', - title='Travel Time Unit', - ), - ] - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class Unit6(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[Unit6, Field(description='Distance unit.', title='Distance Unit')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.', - title='Transport Mode', - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class AvailableRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[AvailableRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class Metric1(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class AttributionWindow1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight for this creative (0–100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description="Optional array of structured placement references where this creative should run within the already-purchased package inventory. New senders SHOULD use this field for placement-level creative routing because placement IDs are publisher-scoped. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. References entries from the product's `placements[]` array by `{ publisher_domain, placement_id }`; if `publisher_domain` is omitted in the ref, receivers MAY interpret it relative to the seller agent's own publisher domain in legacy single-publisher contexts. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description="Legacy shorthand array of placement IDs where this creative should run within the already-purchased package inventory. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. Receivers MAY interpret string IDs relative to the seller agent's own publisher domain in legacy single-publisher contexts. If `placement_refs` is also present, receivers MUST ignore this field.", - min_length=1, - ), - ] = None - - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role19(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role19, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction19(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class FeedFieldMapping1(FeedFieldMapping): - pass - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent1): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class Target7(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent1): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class Target8(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy26(DeclaredBy): - pass - - -class VerifyAgent52(VerifyAgent): - pass - - -class VerifyAgent53(VerifyAgent1): - pass - - -class VerificationItem26(VerificationItem): - pass - - -class DeclaredBy27(DeclaredBy): - pass - - -class VerifyAgent54(VerifyAgent): - pass - - -class VerifyAgent55(VerifyAgent1): - pass - - -class VerificationItem27(VerificationItem): - pass - - -class DeclaredBy28(DeclaredBy): - pass - - -class VerifyAgent56(VerifyAgent): - pass - - -class VerifyAgent57(VerifyAgent1): - pass - - -class VerificationItem28(VerificationItem): - pass - - -class DeclaredBy29(DeclaredBy): - pass - - -class VerifyAgent58(VerifyAgent): - pass - - -class VerifyAgent59(VerifyAgent1): - pass - - -class VerificationItem29(VerificationItem): - pass - - -class DeclaredBy30(DeclaredBy): - pass - - -class VerifyAgent60(VerifyAgent): - pass - - -class VerifyAgent61(VerifyAgent1): - pass - - -class VerificationItem30(VerificationItem): - pass - - -class DeclaredBy31(DeclaredBy): - pass - - -class VerifyAgent62(VerifyAgent): - pass - - -class VerifyAgent63(VerifyAgent1): - pass - - -class VerificationItem31(VerificationItem): - pass - - -class DeclaredBy32(DeclaredBy): - pass - - -class VerifyAgent64(VerifyAgent): - pass - - -class VerifyAgent65(VerifyAgent1): - pass - - -class VerificationItem32(VerificationItem): - pass - - -class DeclaredBy33(DeclaredBy): - pass - - -class VerifyAgent66(VerifyAgent): - pass - - -class VerifyAgent67(VerifyAgent1): - pass - - -class VerificationItem33(VerificationItem): - pass - - -class DeclaredBy34(DeclaredBy): - pass - - -class VerifyAgent68(VerifyAgent): - pass - - -class VerifyAgent69(VerifyAgent1): - pass - - -class VerificationItem34(VerificationItem): - pass - - -class DeclaredBy35(DeclaredBy): - pass - - -class VerifyAgent70(VerifyAgent): - pass - - -class VerifyAgent71(VerifyAgent1): - pass - - -class VerificationItem35(VerificationItem): - pass - - -class DeclaredBy36(DeclaredBy): - pass - - -class VerifyAgent72(VerifyAgent): - pass - - -class VerifyAgent73(VerifyAgent1): - pass - - -class VerificationItem36(VerificationItem): - pass - - -class DeclaredBy37(DeclaredBy): - pass - - -class VerifyAgent74(VerifyAgent): - pass - - -class VerifyAgent75(VerifyAgent1): - pass - - -class VerificationItem37(VerificationItem): - pass - - -class DeclaredBy38(DeclaredBy): - pass - - -class VerifyAgent76(VerifyAgent): - pass - - -class VerifyAgent77(VerifyAgent1): - pass - - -class VerificationItem38(VerificationItem): - pass - - -class DeclaredBy39(DeclaredBy): - pass - - -class VerifyAgent78(VerifyAgent): - pass - - -class VerifyAgent79(VerifyAgent1): - pass - - -class VerificationItem39(VerificationItem): - pass - - -class DeclaredBy40(DeclaredBy): - pass - - -class VerifyAgent80(VerifyAgent): - pass - - -class VerifyAgent81(VerifyAgent1): - pass - - -class VerificationItem40(VerificationItem): - pass - - -class ReferenceAsset1(ReferenceAsset): - pass - - -class FeedFieldMapping2(FeedFieldMapping): - pass - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class DeclaredBy41(DeclaredBy): - pass - - -class VerifyAgent82(VerifyAgent): - pass - - -class VerifyAgent83(VerifyAgent1): - pass - - -class VerificationItem41(VerificationItem): - pass - - -class DeclaredBy42(DeclaredBy): - pass - - -class VerifyAgent84(VerifyAgent): - pass - - -class VerifyAgent85(VerifyAgent1): - pass - - -class VerificationItem42(VerificationItem): - pass - - -class DeclaredBy43(DeclaredBy): - pass - - -class VerifyAgent86(VerifyAgent): - pass - - -class VerifyAgent87(VerifyAgent1): - pass - - -class VerificationItem43(VerificationItem): - pass - - -class DeclaredBy44(DeclaredBy): - pass - - -class VerifyAgent88(VerifyAgent): - pass - - -class VerifyAgent89(VerifyAgent1): - pass - - -class VerificationItem44(VerificationItem): - pass - - -class DeclaredBy45(DeclaredBy): - pass - - -class VerifyAgent90(VerifyAgent): - pass - - -class VerifyAgent91(VerifyAgent1): - pass - - -class VerificationItem45(VerificationItem): - pass - - -class DeclaredBy46(DeclaredBy): - pass - - -class VerifyAgent92(VerifyAgent): - pass - - -class VerifyAgent93(VerifyAgent1): - pass - - -class VerificationItem46(VerificationItem): - pass - - -class DeclaredBy47(DeclaredBy): - pass - - -class VerifyAgent94(VerifyAgent): - pass - - -class VerifyAgent95(VerifyAgent1): - pass - - -class VerificationItem47(VerificationItem): - pass - - -class DeclaredBy48(DeclaredBy): - pass - - -class VerifyAgent96(VerifyAgent): - pass - - -class VerifyAgent97(VerifyAgent1): - pass - - -class VerificationItem48(VerificationItem): - pass - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to apply for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class Status2(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class Type1(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type1, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy49(DeclaredBy): - pass - - -class VerifyAgent98(VerifyAgent): - pass - - -class VerifyAgent99(VerifyAgent1): - pass - - -class VerificationItem49(VerificationItem): - pass - - - -class DeclaredBy50(DeclaredBy): - pass - - -class VerifyAgent100(VerifyAgent): - pass - - -class VerifyAgent101(VerifyAgent1): - pass - - -class VerificationItem50(VerificationItem): - pass - - -class DeclaredBy51(DeclaredBy): - pass - - -class VerifyAgent102(VerifyAgent): - pass - - -class VerifyAgent103(VerifyAgent1): - pass - - -class VerificationItem51(VerificationItem): - pass - - -class DeclaredBy52(DeclaredBy): - pass - - -class VerifyAgent104(VerifyAgent): - pass - - -class VerifyAgent105(VerifyAgent1): - pass - - -class VerificationItem52(VerificationItem): - pass - - -class DeclaredBy53(DeclaredBy): - pass - - -class VerifyAgent106(VerifyAgent): - pass - - -class VerifyAgent107(VerifyAgent1): - pass - - -class VerificationItem53(VerificationItem): - pass - - -class DeclaredBy54(DeclaredBy): - pass - - -class VerifyAgent108(VerifyAgent): - pass - - -class VerifyAgent109(VerifyAgent1): - pass - - -class VerificationItem54(VerificationItem): - pass - - -class DeclaredBy55(DeclaredBy): - pass - - -class VerifyAgent110(VerifyAgent): - pass - - -class VerifyAgent111(VerifyAgent1): - pass - - -class VerificationItem55(VerificationItem): - pass - - -class DeclaredBy56(DeclaredBy): - pass - - -class VerifyAgent112(VerifyAgent): - pass - - -class VerifyAgent113(VerifyAgent1): - pass - - -class VerificationItem56(VerificationItem): - pass - - -class DeclaredBy57(DeclaredBy): - pass - - -class VerifyAgent114(VerifyAgent): - pass - - -class VerifyAgent115(VerifyAgent1): - pass - - -class VerificationItem57(VerificationItem): - pass - - -class DeclaredBy58(DeclaredBy): - pass - - -class VerifyAgent116(VerifyAgent): - pass - - -class VerifyAgent117(VerifyAgent1): - pass - - -class VerificationItem58(VerificationItem): - pass - - -class DeclaredBy59(DeclaredBy): - pass - - -class VerifyAgent118(VerifyAgent): - pass - - -class VerifyAgent119(VerifyAgent1): - pass - - -class VerificationItem59(VerificationItem): - pass - - -class DeclaredBy60(DeclaredBy): - pass - - -class VerifyAgent120(VerifyAgent): - pass - - -class VerifyAgent121(VerifyAgent1): - pass - - -class VerificationItem60(VerificationItem): - pass - - -class DeclaredBy61(DeclaredBy): - pass - - -class VerifyAgent122(VerifyAgent): - pass - - -class VerifyAgent123(VerifyAgent1): - pass - - -class VerificationItem61(VerificationItem): - pass - - -class DeclaredBy62(DeclaredBy): - pass - - -class VerifyAgent124(VerifyAgent): - pass - - -class VerifyAgent125(VerifyAgent1): - pass - - -class VerificationItem62(VerificationItem): - pass - - -class DeclaredBy63(DeclaredBy): - pass - - -class VerifyAgent126(VerifyAgent): - pass - - -class VerifyAgent127(VerifyAgent1): - pass - - -class VerificationItem63(VerificationItem): - pass - - -class ReferenceAsset2(ReferenceAsset): - pass - - -class FeedFieldMapping3(FeedFieldMapping): - pass - - -class ReferenceAuthorization2(ReferenceAuthorization): - pass - - -class DeclaredBy64(DeclaredBy): - pass - - -class VerifyAgent128(VerifyAgent): - pass - - -class VerifyAgent129(VerifyAgent1): - pass - - -class VerificationItem64(VerificationItem): - pass - - -class DeclaredBy65(DeclaredBy): - pass - - -class VerifyAgent130(VerifyAgent): - pass - - -class VerifyAgent131(VerifyAgent1): - pass - - -class VerificationItem65(VerificationItem): - pass - - -class DeclaredBy66(DeclaredBy): - pass - - -class VerifyAgent132(VerifyAgent): - pass - - -class VerifyAgent133(VerifyAgent1): - pass - - -class VerificationItem66(VerificationItem): - pass - - -class DeclaredBy67(DeclaredBy): - pass - - -class VerifyAgent134(VerifyAgent): - pass - - -class VerifyAgent135(VerifyAgent1): - pass - - -class VerificationItem67(VerificationItem): - pass - - -class DeclaredBy68(DeclaredBy): - pass - - -class VerifyAgent136(VerifyAgent): - pass - - -class VerifyAgent137(VerifyAgent1): - pass - - -class VerificationItem68(VerificationItem): - pass - - -class DeclaredBy69(DeclaredBy): - pass - - -class VerifyAgent138(VerifyAgent): - pass - - -class VerifyAgent139(VerifyAgent1): - pass - - -class VerificationItem69(VerificationItem): - pass - - -class DeclaredBy70(DeclaredBy): - pass - - -class VerifyAgent140(VerifyAgent): - pass - - -class VerifyAgent141(VerifyAgent1): - pass - - -class VerificationItem70(VerificationItem): - pass - - -class DeclaredBy71(DeclaredBy): - pass - - -class VerifyAgent142(VerifyAgent): - pass - - -class VerifyAgent143(VerifyAgent1): - pass - - -class VerificationItem71(VerificationItem): - pass - - -class DeclaredBy72(DeclaredBy): - pass - - -class VerifyAgent144(VerifyAgent): - pass - - -class VerifyAgent145(VerifyAgent1): - pass - - -class VerificationItem72(VerificationItem): - pass - - -class DeclaredBy73(DeclaredBy): - pass - - -class VerifyAgent146(VerifyAgent): - pass - - -class VerifyAgent147(VerifyAgent1): - pass - - -class VerificationItem73(VerificationItem): - pass - - -class DeclaredBy74(DeclaredBy): - pass - - -class VerifyAgent148(VerifyAgent): - pass - - -class VerifyAgent149(VerifyAgent1): - pass - - -class VerificationItem74(VerificationItem): - pass - - -class DeclaredBy75(DeclaredBy): - pass - - -class VerifyAgent150(VerifyAgent): - pass - - -class VerifyAgent151(VerifyAgent1): - pass - - -class VerificationItem75(VerificationItem): - pass - - -class DeclaredBy76(DeclaredBy): - pass - - -class VerifyAgent152(VerifyAgent): - pass - - -class VerifyAgent153(VerifyAgent1): - pass - - -class VerificationItem76(VerificationItem): - pass - - -class DeclaredBy77(DeclaredBy): - pass - - -class VerifyAgent154(VerifyAgent): - pass - - -class VerifyAgent155(VerifyAgent1): - pass - - -class VerificationItem77(VerificationItem): - pass - - -class DeclaredBy78(DeclaredBy): - pass - - -class VerifyAgent156(VerifyAgent): - pass - - -class VerifyAgent157(VerifyAgent1): - pass - - -class VerificationItem78(VerificationItem): - pass - - -class DeclaredBy79(DeclaredBy): - pass - - -class VerifyAgent158(VerifyAgent): - pass - - -class VerifyAgent159(VerifyAgent1): - pass - - -class VerificationItem79(VerificationItem): - pass - - -class DeclaredBy80(DeclaredBy): - pass - - -class VerifyAgent160(VerifyAgent): - pass - - -class VerifyAgent161(VerifyAgent1): - pass - - -class VerificationItem80(VerificationItem): - pass - - -class DeclaredBy81(DeclaredBy): - pass - - -class VerifyAgent162(VerifyAgent): - pass - - -class VerifyAgent163(VerifyAgent1): - pass - - -class VerificationItem81(VerificationItem): - pass - - -class DeclaredBy82(DeclaredBy): - pass - - -class VerifyAgent164(VerifyAgent): - pass - - -class VerifyAgent165(VerifyAgent1): - pass - - -class VerificationItem82(VerificationItem): - pass - - -class DeclaredBy83(DeclaredBy): - pass - - -class VerifyAgent166(VerifyAgent): - pass - - -class VerifyAgent167(VerifyAgent1): - pass - - -class VerificationItem83(VerificationItem): - pass - - -class DeclaredBy84(DeclaredBy): - pass - - -class VerifyAgent168(VerifyAgent): - pass - - -class VerifyAgent169(VerifyAgent1): - pass - - -class VerificationItem84(VerificationItem): - pass - - -class DeclaredBy85(DeclaredBy): - pass - - -class VerifyAgent170(VerifyAgent): - pass - - -class VerifyAgent171(VerifyAgent1): - pass - - -class VerificationItem85(VerificationItem): - pass - - -class ReferenceAsset3(ReferenceAsset): - pass - - -class FeedFieldMapping4(FeedFieldMapping): - pass - - -class ReferenceAuthorization3(ReferenceAuthorization): - pass - - -class DeclaredBy86(DeclaredBy): - pass - - -class VerifyAgent172(VerifyAgent): - pass - - -class VerifyAgent173(VerifyAgent1): - pass - - -class VerificationItem86(VerificationItem): - pass - - -class DeclaredBy87(DeclaredBy): - pass - - -class VerifyAgent174(VerifyAgent): - pass - - -class VerifyAgent175(VerifyAgent1): - pass - - -class VerificationItem87(VerificationItem): - pass - - -class DeclaredBy88(DeclaredBy): - pass - - -class VerifyAgent176(VerifyAgent): - pass - - -class VerifyAgent177(VerifyAgent1): - pass - - -class VerificationItem88(VerificationItem): - pass - - -class DeclaredBy89(DeclaredBy): - pass - - -class VerifyAgent178(VerifyAgent): - pass - - -class VerifyAgent179(VerifyAgent1): - pass - - -class VerificationItem89(VerificationItem): - pass - - -class DeclaredBy90(DeclaredBy): - pass - - -class VerifyAgent180(VerifyAgent): - pass - - -class VerifyAgent181(VerifyAgent1): - pass - - -class VerificationItem90(VerificationItem): - pass - - -class DeclaredBy91(DeclaredBy): - pass - - -class VerifyAgent182(VerifyAgent): - pass - - -class VerifyAgent183(VerifyAgent1): - pass - - -class VerificationItem91(VerificationItem): - pass - - -class DeclaredBy92(DeclaredBy): - pass - - -class VerifyAgent184(VerifyAgent): - pass - - -class VerifyAgent185(VerifyAgent1): - pass - - -class VerificationItem92(VerificationItem): - pass - - -class DeclaredBy93(DeclaredBy): - pass - - -class VerifyAgent186(VerifyAgent): - pass - - -class VerifyAgent187(VerifyAgent1): - pass - - -class VerificationItem93(VerificationItem): - pass - - -class IndustryIdentifier1(IndustryIdentifier): - pass - - -class DeclaredBy94(DeclaredBy): - pass - - -class VerifyAgent188(VerifyAgent): - pass - - -class VerifyAgent189(VerifyAgent1): - pass - - -class VerificationItem94(VerificationItem): - pass - - -class DeclaredBy95(DeclaredBy): - pass - - -class VerifyAgent190(VerifyAgent): - pass - - -class VerifyAgent191(VerifyAgent1): - pass - - -class VerificationItem95(VerificationItem): - pass - - -class AdvertiserIndustry(StrEnum): - automotive = 'automotive' - automotive_electric_vehicles = 'automotive.electric_vehicles' - automotive_parts_accessories = 'automotive.parts_accessories' - automotive_luxury = 'automotive.luxury' - beauty_cosmetics = 'beauty_cosmetics' - beauty_cosmetics_skincare = 'beauty_cosmetics.skincare' - beauty_cosmetics_fragrance = 'beauty_cosmetics.fragrance' - beauty_cosmetics_haircare = 'beauty_cosmetics.haircare' - cannabis = 'cannabis' - cpg = 'cpg' - cpg_personal_care = 'cpg.personal_care' - cpg_household = 'cpg.household' - dating = 'dating' - education = 'education' - education_higher_education = 'education.higher_education' - education_online_learning = 'education.online_learning' - education_k12 = 'education.k12' - energy_utilities = 'energy_utilities' - energy_utilities_renewable = 'energy_utilities.renewable' - fashion_apparel = 'fashion_apparel' - fashion_apparel_luxury = 'fashion_apparel.luxury' - fashion_apparel_sportswear = 'fashion_apparel.sportswear' - finance = 'finance' - finance_banking = 'finance.banking' - finance_insurance = 'finance.insurance' - finance_investment = 'finance.investment' - finance_cryptocurrency = 'finance.cryptocurrency' - food_beverage = 'food_beverage' - food_beverage_alcohol = 'food_beverage.alcohol' - food_beverage_restaurants = 'food_beverage.restaurants' - food_beverage_packaged_goods = 'food_beverage.packaged_goods' - gambling_betting = 'gambling_betting' - gambling_betting_sports_betting = 'gambling_betting.sports_betting' - gambling_betting_casino = 'gambling_betting.casino' - gaming = 'gaming' - gaming_mobile = 'gaming.mobile' - gaming_console_pc = 'gaming.console_pc' - gaming_esports = 'gaming.esports' - government_nonprofit = 'government_nonprofit' - government_nonprofit_political = 'government_nonprofit.political' - government_nonprofit_charity = 'government_nonprofit.charity' - healthcare = 'healthcare' - healthcare_pharmaceutical = 'healthcare.pharmaceutical' - healthcare_medical_devices = 'healthcare.medical_devices' - healthcare_wellness = 'healthcare.wellness' - home_garden = 'home_garden' - home_garden_furniture = 'home_garden.furniture' - home_garden_home_improvement = 'home_garden.home_improvement' - media_entertainment = 'media_entertainment' - media_entertainment_podcasts = 'media_entertainment.podcasts' - media_entertainment_music = 'media_entertainment.music' - media_entertainment_film_tv = 'media_entertainment.film_tv' - media_entertainment_publishing = 'media_entertainment.publishing' - media_entertainment_live_events = 'media_entertainment.live_events' - pets = 'pets' - professional_services = 'professional_services' - professional_services_legal = 'professional_services.legal' - professional_services_consulting = 'professional_services.consulting' - real_estate = 'real_estate' - real_estate_residential = 'real_estate.residential' - real_estate_commercial = 'real_estate.commercial' - recruitment_hr = 'recruitment_hr' - retail = 'retail' - retail_ecommerce = 'retail.ecommerce' - retail_department_stores = 'retail.department_stores' - sports_fitness = 'sports_fitness' - sports_fitness_equipment = 'sports_fitness.equipment' - sports_fitness_teams_leagues = 'sports_fitness.teams_leagues' - technology = 'technology' - technology_software = 'technology.software' - technology_hardware = 'technology.hardware' - technology_ai_ml = 'technology.ai_ml' - telecom = 'telecom' - telecom_mobile_carriers = 'telecom.mobile_carriers' - telecom_internet_providers = 'telecom.internet_providers' - transportation_logistics = 'transportation_logistics' - travel_hospitality = 'travel_hospitality' - travel_hospitality_airlines = 'travel_hospitality.airlines' - travel_hospitality_hotels = 'travel_hospitality.hotels' - travel_hospitality_cruise = 'travel_hospitality.cruise' - travel_hospitality_tourism = 'travel_hospitality.tourism' - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role100(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role100, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class InvoiceRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IoAcceptance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, Field(description="The io_id from the proposal's insertion_order being accepted") - ] - accepted_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when the IO was accepted') - ] - signatory: Annotated[ - str, - Field( - description='Who accepted the IO — agent identifier or human name', - max_length=250, - min_length=1, - ), - ] - signature_id: Annotated[ - str | None, - Field( - description='Reference to the electronic signature from the signing service, when signing_url was used' - ), - ] = None - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class DeliveryMode(StrEnum): - realtime = 'realtime' - batched = 'batched' - - -class BatchFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class OptimizationGoals1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target1 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: EventType - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When present without a value-oriented target, the seller may use it for delivery reporting (conversion_value, roas) but must not change the optimization objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - float | None, - Field( - description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class OptimizationGoals2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target2 | Target3 | Target4 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target5 | Target6 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals(RootModel[OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3]): - root: Annotated[ - OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas12(GeoPostalAreas1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas13(GeoPostalAreas2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas14(GeoPostalAreas3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas15(GeoPostalAreas4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas16(GeoPostalAreas5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas17(GeoPostalAreas6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas18(GeoPostalAreas7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas19(GeoPostalAreas8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas20(GeoPostalAreas9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas21(GeoPostalAreas10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21 - ] -): - root: Annotated[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude11(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude12(GeoPostalAreasExclude1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude13(GeoPostalAreasExclude2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude14(GeoPostalAreasExclude3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude15(GeoPostalAreasExclude4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude16(GeoPostalAreasExclude5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude17(GeoPostalAreasExclude6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude18(GeoPostalAreasExclude7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude19(GeoPostalAreasExclude8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude20(GeoPostalAreasExclude9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude21(GeoPostalAreasExclude10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21 - ] -): - root: Annotated[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude22(GeoPostalAreas22): - pass - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window1 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas22] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude22] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatformEnum] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor1, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric1, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor2, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: Annotated[ - CompletionSource | None, - Field( - description="Trust-source disambiguator for `completion_rate` — *who* attested to the completion event, not *how* (methodology granularity is a separate dimension; future qualifier keys may add it if buyer demand surfaces). The two paths can yield materially different rates, particularly in SSAI environments where the player's view of completion may differ from a vendor's. Used as a `qualifier.completion_source` key on `committed_metrics`, `missing_metrics`, and `metric_aggregates` to disambiguate which trust source the row represents. Edge cases: walled gardens where the seller is also the measurement vendor (YouTube, Spotify) collapse to `seller_attested` by trust-model logic — the same party served and counted. IAB-certified first-party podcast measurement (Podtrac, Triton on their own platforms; Art19 on its own platform) likewise collapses to `seller_attested`. The same vendor's offering on a third-party platform (Podtrac on a publisher who isn't Podtrac) is `vendor_attested`. The trust axis is *not* who runs the SDK — it's who is independent of the seller's revenue interest.", - title='Completion Source', - ), - ] = None - attribution_methodology: Annotated[ - AttributionMethodology | None, - Field( - description='How attribution between ad exposure and outcome events was computed. Used as a `qualifier.attribution_methodology` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate the same outcome metric reported under different methodologies — `conversion_value` measured deterministically (matched purchase IDs) is not the same number as `conversion_value` measured probabilistically (modeled match) and should never be summed across methodologies. The retail-media closed-loop pattern typically reports under `deterministic_purchase`; MMM and clean-room outputs typically report under `modeled` or `probabilistic`; panel-based measurement (Nielsen, comScore, Edison) reports under `panel_based`.', - title='Attribution Methodology', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow1 | None, - Field( - description="A time duration expressed as an interval and unit. Used for frequency cap windows, attribution windows, reach optimization windows, time budgets, and other time-based settings. When unit is 'campaign', interval must be 1 — the window spans the full campaign flight.", - title='Duration', - ), - ] = None - lift_dimension: Annotated[ - LiftDimension | None, - Field( - description='Brand-lift dimension disambiguator. Brand lift is multidimensional in production — Kantar, Upwave, Cint, DoubleVerify, and similar vendors report awareness, consideration, favorability, purchase intent, and ad recall as separate measurements with their own sample sizes and confidence intervals. Used as a `qualifier.lift_dimension` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate which dimension of `brand_lift` a row represents. Two `brand_lift` rows under different lift dimensions represent different surveyed outcomes and must not be combined into a single number.', - title='Lift Dimension', - ), - ] = None - - -class CommittedMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description='Disambiguator — same shape as on the response-side `committed_metrics`. Required when the buyer wants to pin a specific measurement path: `viewability_standard` for MRC vs GroupM viewability; `completion_source` for seller- vs vendor-attested `completion_rate`; `attribution_methodology` for how attribution was computed (deterministic_purchase, probabilistic, panel_based, modeled); `attribution_window` for the time window over which outcomes are attributed. See response-side description for full semantics.' - ), - ] = None - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo4 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride4 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor3, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. MUST be present in the product's `reporting_capabilities.vendor_metrics` for the same vendor.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class CommittedMetrics(RootModel[CommittedMetrics1 | CommittedMetrics2]): - root: Annotated[CommittedMetrics1 | CommittedMetrics2, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction16(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction18(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets15(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets16(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping1] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media1, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction26(Jurisdiction): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction26] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent52 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent53 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction27(Jurisdiction): - pass - - -class Disclosure26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction27] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy26 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem26] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark26] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure26 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem26] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance26 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent54 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent55 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction28(Jurisdiction): - pass - - -class Disclosure27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction28] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy27 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem27] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark27] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure27 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem27] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance27 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent56 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent57 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction29(Jurisdiction): - pass - - -class Disclosure28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction29] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy28 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem28] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark28] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure28 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem28] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance28 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent58 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent59 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction30(Jurisdiction): - pass - - -class Disclosure29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction30] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy29 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem29] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark29] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure29 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem29] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance29 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent60 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent61 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction31(Jurisdiction): - pass - - -class Disclosure30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction31] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy30 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem30] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark30] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure30 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem30] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance30 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent62 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent63 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction32(Jurisdiction): - pass - - -class Disclosure31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction32] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy31 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem31] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark31] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure31 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem31] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance31 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent64 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent65 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction33(Jurisdiction): - pass - - -class Disclosure32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction33] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy32 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem32] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark32] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure32 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem32] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance32 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent66 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent67 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction34(Jurisdiction): - pass - - -class Disclosure33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction34] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy33 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem33] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark33] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure33 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem33] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance33 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent68 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent69 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction35(Jurisdiction): - pass - - -class Disclosure34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction35] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy34 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem34] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark34] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure34 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem34] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance34 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent70 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent71 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction36(Jurisdiction): - pass - - -class Disclosure35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction36] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy35 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem35] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark35] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure35 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem35] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance35 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent72 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent73 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction37(Jurisdiction): - pass - - -class Disclosure36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction37] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy36 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem36] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark36] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure36 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem36] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance36 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent74 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent75 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction38(Jurisdiction): - pass - - -class Disclosure37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction38] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy37 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem37] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark37] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure37 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem37] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance37 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent76 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent77 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction39(Jurisdiction): - pass - - -class Disclosure38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction39] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy38 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem38] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark38] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure38 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem38] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance38 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent78 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent79 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction40(Jurisdiction): - pass - - -class Disclosure39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction40] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy39 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem39] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark39] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure39 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem39] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance39 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent80 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent81 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction41(Jurisdiction): - pass - - -class Disclosure40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction41] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy40 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem40] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark40] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure40 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem40] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance40 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure1(RequiredDisclosure): - pass - - -class Compliance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure1] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets2217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset1] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance1 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets2218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping2] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent82 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent83 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction43(Jurisdiction): - pass - - -class Disclosure41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction43] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy41 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem41] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark41] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure41 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem41] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance41 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent84 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent85 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction44(Jurisdiction): - pass - - -class Disclosure42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction44] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy42 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem42] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark42] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure42 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem42] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance42 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent86 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent87 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction45(Jurisdiction): - pass - - -class Disclosure43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction45] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy43 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem43] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark43] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure43 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem43] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance43 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent88 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent89 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction46(Jurisdiction): - pass - - -class Disclosure44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction46] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy44 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem44] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark44] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure44 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem44] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance44 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent90 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent91 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction47(Jurisdiction): - pass - - -class Disclosure45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction47] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy45 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem45] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark45] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure45 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem45] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media2 | Media3, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl1 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance45 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent92 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent93 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction48(Jurisdiction): - pass - - -class Disclosure46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction48] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy46 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem46] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark46] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure46 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem46] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance46 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent94 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent95 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction49(Jurisdiction): - pass - - -class Disclosure47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction49] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy47 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem47] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark47] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure47 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem47] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance47 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent96 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent97 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction50(Jurisdiction): - pass - - -class Disclosure48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction50] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy48 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem48] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark48] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure48 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem48] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance48 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets221( - RootModel[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223 - ] -): - root: Annotated[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets22(RootModel[list[Assets221]]): - root: Annotated[list[Assets221], Field(min_length=1)] - - -class EmbeddedProvenanceItem49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent98 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent99 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction51(Jurisdiction): - pass - - -class Disclosure49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction51] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy49 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem49] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark49] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure49 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem49] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets1 - | Assets2 - | Assets3 - | Assets4 - | Assets5 - | Assets6 - | Assets7 - | Assets8 - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - | Assets16 - | Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status2 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance49 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction52(Jurisdiction): - pass - - -class Disclosure50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction52] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy50 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem50] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark50] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure50 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem50] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance50 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction53(Jurisdiction): - pass - - -class Disclosure51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction53] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy51 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem51] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark51] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure51 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem51] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance51 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction54(Jurisdiction): - pass - - -class Disclosure52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction54] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy52 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem52] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark52] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure52 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem52] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance52 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction55(Jurisdiction): - pass - - -class Disclosure53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction55] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy53 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem53] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark53] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure53 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem53] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance53 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction56(Jurisdiction): - pass - - -class Disclosure54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction56] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy54 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem54] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark54] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure54 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem54] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance54 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction57(Jurisdiction): - pass - - -class Disclosure55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction57] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy55 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem55] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark55] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure55 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem55] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance55 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction58(Jurisdiction): - pass - - -class Disclosure56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction58] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy56 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem56] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark56] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure56 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem56] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance56 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction59(Jurisdiction): - pass - - -class Disclosure57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction59] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy57 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem57] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark57] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure57 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem57] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance57 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction60(Jurisdiction): - pass - - -class Disclosure58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction60] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy58 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem58] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark58] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure58 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem58] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance58 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction61(Jurisdiction): - pass - - -class Disclosure59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction61] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy59 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem59] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark59] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure59 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem59] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance59 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction62(Jurisdiction): - pass - - -class Disclosure60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction62] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy60 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem60] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark60] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure60 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem60] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance60 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction63(Jurisdiction): - pass - - -class Disclosure61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction63] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy61 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem61] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark61] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure61 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem61] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance61 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction64(Jurisdiction): - pass - - -class Disclosure62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction64] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy62 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem62] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark62] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure62 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem62] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance62 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction65(Jurisdiction): - pass - - -class Disclosure63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction65] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy63 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem63] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark63] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure63 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem63] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance63 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets37(Assets14): - pass - - -class RequiredDisclosure2(RequiredDisclosure): - pass - - -class Compliance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure2] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets38(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset2] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance2 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets39(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping3] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction67(Jurisdiction): - pass - - -class Disclosure64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction67] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy64 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem64] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark64] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure64 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem64] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization2 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance64 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction68(Jurisdiction): - pass - - -class Disclosure65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction68] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy65 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem65] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark65] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure65 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem65] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance65 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction69(Jurisdiction): - pass - - -class Disclosure66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction69] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy66 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem66] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark66] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure66 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem66] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance66 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction70(Jurisdiction): - pass - - -class Disclosure67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction70] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy67 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem67] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark67] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure67 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem67] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance67 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction71(Jurisdiction): - pass - - -class Disclosure68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction71] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy68 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem68] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark68] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure68 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem68] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media4 | Media5, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl2 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance68 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction72(Jurisdiction): - pass - - -class Disclosure69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction72] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy69 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem69] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark69] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure69 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem69] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance69 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction73(Jurisdiction): - pass - - -class Disclosure70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction73] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy70 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem70] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark70] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure70 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem70] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance70 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction74(Jurisdiction): - pass - - -class Disclosure71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction74] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy71 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem71] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark71] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure71 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem71] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance71 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction75(Jurisdiction): - pass - - -class Disclosure72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction75] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy72 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem72] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark72] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure72 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem72] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance72 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction76(Jurisdiction): - pass - - -class Disclosure73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction76] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy73 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem73] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark73] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure73 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem73] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance73 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction77(Jurisdiction): - pass - - -class Disclosure74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction77] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy74 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem74] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark74] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure74 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem74] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance74 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction78(Jurisdiction): - pass - - -class Disclosure75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction78] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy75 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem75] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark75] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure75 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem75] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance75 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction79(Jurisdiction): - pass - - -class Disclosure76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction79] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy76 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem76] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark76] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure76 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem76] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance76 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction80(Jurisdiction): - pass - - -class Disclosure77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction80] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy77 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem77] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark77] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure77 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem77] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance77 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction81(Jurisdiction): - pass - - -class Disclosure78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction81] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy78 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem78] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark78] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure78 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem78] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance78 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction82(Jurisdiction): - pass - - -class Disclosure79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction82] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy79 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem79] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark79] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure79 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem79] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance79 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction83(Jurisdiction): - pass - - -class Disclosure80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction83] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy80 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem80] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark80] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure80 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem80] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance80 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction84(Jurisdiction): - pass - - -class Disclosure81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction84] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy81 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem81] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark81] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure81 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem81] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance81 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction85(Jurisdiction): - pass - - -class Disclosure82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction85] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy82 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem82] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark82] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure82 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem82] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance82 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction86(Jurisdiction): - pass - - -class Disclosure83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction86] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy83 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem83] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark83] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure83 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem83] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance83 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction87(Jurisdiction): - pass - - -class Disclosure84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction87] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy84 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem84] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark84] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure84 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem84] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance84 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction88(Jurisdiction): - pass - - -class Disclosure85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction88] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy85 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem85] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark85] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure85 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem85] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance85 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure3(RequiredDisclosure): - pass - - -class Compliance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure3] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets4517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset3] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance3 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets4518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping4] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction90(Jurisdiction): - pass - - -class Disclosure86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction90] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy86 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem86] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark86] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure86 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem86] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization3 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance86 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction91(Jurisdiction): - pass - - -class Disclosure87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction91] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy87 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem87] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark87] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure87 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem87] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance87 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction92(Jurisdiction): - pass - - -class Disclosure88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction92] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy88 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem88] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark88] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure88 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem88] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance88 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction93(Jurisdiction): - pass - - -class Disclosure89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction93] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy89 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem89] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark89] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure89 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem89] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance89 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction94(Jurisdiction): - pass - - -class Disclosure90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction94] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy90 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem90] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark90] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure90 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem90] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media6 | Media7, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl3 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance90 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction95(Jurisdiction): - pass - - -class Disclosure91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction95] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy91 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem91] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark91] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure91 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem91] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance91 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction96(Jurisdiction): - pass - - -class Disclosure92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction96] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy92 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem92] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark92] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure92 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem92] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance92 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction97(Jurisdiction): - pass - - -class Disclosure93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction97] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy93 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem93] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark93] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure93 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem93] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance93 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets451( - RootModel[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523 - ] -): - root: Annotated[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets45(RootModel[list[Assets451]]): - root: Annotated[list[Assets451], Field(min_length=1)] - - -class EmbeddedProvenanceItem94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction98(Jurisdiction): - pass - - -class Disclosure94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction98] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy94 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem94] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark94] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure94 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem94] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId | None, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - | Assets30 - | Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status2 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier1] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance94 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - product_id: Annotated[ - str, - Field( - description='Product ID for this package. Sellers MUST echo this value on every response package object that represents this requested package.' - ), - ] - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format selector. Array of format IDs that will be used for this package - must be supported by the product. If omitted (and no 3.1+ format-option selector or direct canonical selector is present), defaults to all formats supported by the product.\n\nSellers comparing this selector to a product's `format_options[]` MUST first normalize each legacy `format_id` through the canonical mapping path (`canonical`, `v1_format_ref`, or registry projection). Exact `(agent_url, id)` comparison after projection is insufficient: a legacy fixed-size display ID can satisfy a canonical `image` product declaration with matching `width`/`height`. Product gating remains directional: if the product declares fixed dimensions or duration, the selected format must declare and match those constraints; an under-specified canonical request is not a wildcard for a fixed-size or fixed-duration product. Range constraints use containment, not overlap: a range-based request satisfies the product only when every value it permits falls within the product's accepted range.", - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs] | None, - Field( - description='3.1+ format-option selector. Array of structured format option references, each matching one of the target product\'s `format_options[]` entries. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. If omitted along with `format_ids` and direct `format_kind`, all product formats are active.\n\n**Resolution rules (normative).**\n- **Both `format_option_refs` and `format_ids` present.** `format_option_refs` wins; the seller routes by structured references and MUST NOT validate `format_ids` for consistency with the resolved declarations. The `format_ids` value is a legacy-compat hint for intermediaries on the wire path; the resolving seller ignores it.\n- **`format_option_refs` only.** Seller looks up each entry against the package\'s target product `format_options[]` and uses the matching declaration (and that declaration\'s `v1_format_ref[]` when projecting to legacy named-format surfaces). This is the 3.1+ format-option authoring path. `scope: "product"` is scoped only by this target product; it is not a seller-wide identifier.\n- **`format_ids` only.** Existing named-format behavior; unchanged.\n- **`format_kind` only.** Direct canonical selector behavior; seller compares `{ format_kind, params }` against the product\'s `format_options[]` declarations using directional product satisfaction.\n- **None of `format_option_refs`, `format_ids`, or `format_kind`.** Default — all formats supported by the product are active.\n\n**Failure modes (normative).** Sellers MUST reject with `UNSUPPORTED_FEATURE` (with `field` pointing at the failing package and entry, e.g. `packages[0].format_option_refs[1]`) when:\n- Any entry references a format option not present in the target product\'s `format_options[]`, OR\n- The target product carries `format_ids` but no `format_options[]` (legacy-format-only product — there is no closed set to resolve against), OR\n- The target product carries `format_options[]` but none of the entries publish selectable `format_option_id` values. Sellers SHOULD set `error.details.reason` to `format_option_refs_not_published` in this case so buyers can distinguish it from an outright mismatch and fall back to `format_ids[]`.\n\n**Seller obligation.** For buyers to use the 3.1+ format-option path against a product, the seller MUST publish a selectable `format_option_id` on each `format_options[]` entry it expects buyers to select; if the option is publisher-catalog backed, include `publisher_domain` on the product declaration and require buyers to use `scope: "publisher"` in `FormatOptionRefs1`.\n\n**No legacy capability selector.** `capability_ids` was removed before GA; schemas reject it instead of treating it as an extension.\n\n**Dual emission.** Format-option-aware buyer SDKs targeting a heterogeneous seller population SHOULD emit `format_ids` alongside `format_option_refs` so legacy-format-only sellers — which ignore unknown fields per `additionalProperties: true` — still receive an explicit format set rather than silently defaulting to all formats supported by the product.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Omit when selecting by `format_option_refs` or `format_ids`; those selectors resolve their parameters from the product declaration or legacy catalog projection." - ), - ] = None - budget: Annotated[ - float, - Field(description="Budget allocation for this package in the media buy's currency", ge=0.0), - ] - pacing: Annotated[ - Pacing | None, Field(description='Budget pacing strategy', title='Pacing') - ] = None - pricing_option_id: Annotated[ - str, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - catalogs: Annotated[ - list[Catalog] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' - ), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics] | None, - Field( - description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment] | None, - Field( - description='Assign existing library creatives to this package with optional weights and placement targeting', - min_length=1, - ), - ] = None - creatives: Annotated[ - list[Creatives | Creatives1] | None, - Field( - description="Upload creative assets inline and assign to this package. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', - max_length=100, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction99(Jurisdiction): - pass - - -class Disclosure95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction99] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy95 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem95] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark95] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure95 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem95] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance95 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo5 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride5 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Authentication1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class ReportingWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for reporting notifications')] - token: Annotated[ - str | None, - Field( - description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication1, - Field( - description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path." - ), - ] - reporting_frequency: Annotated[ - ReportingFrequency, - Field( - description='Frequency for automated reporting delivery. Must be supported by all products in the media buy.' - ), - ] - requested_metrics: Annotated[ - list[AvailableMetric] | None, - Field( - description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included. Must be subset of product's available_metrics." - ), - ] = None - - -class ArtifactWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for artifact delivery')] - token: Annotated[ - str | None, - Field( - description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication1, - Field( - description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path." - ), - ] - delivery_mode: Annotated[ - DeliveryMode, - Field( - description="How artifacts are delivered. 'realtime' pushes artifacts as impressions occur. 'batched' aggregates artifacts and pushes periodically (see batch_frequency)." - ), - ] - batch_frequency: Annotated[ - BatchFrequency | None, - Field( - description="For batched delivery, how often to push artifacts. Required when delivery_mode is 'batched'." - ), - ] = None - sampling_rate: Annotated[ - float | None, - Field( - description='Fraction of impressions to include (0-1). 1.0 = all impressions, 0.1 = 10% sample. Default: 1.0', - ge=0.0, - le=1.0, - ), - ] = None - - -class CreateMediaBuyRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. If a request with the same idempotency_key and account has already been processed, the seller returns the existing media buy rather than creating a duplicate. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - plan_id: Annotated[ - str | None, - Field( - description='Campaign governance plan identifier. Required when the account has governance_agents. The seller includes this in the committed check_governance request so the governance agent can validate against the correct plan.' - ), - ] = None - account: Annotated[ - Account | Account1, - Field( - description='Account to bill for this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - proposal_id: Annotated[ - str | None, - Field( - description="ID of a committed proposal from get_products to execute. When provided with total_budget, the publisher converts the proposal's allocation percentages into packages automatically. Alternative to providing packages array. If the referenced proposal has proposal_status: 'draft', the seller MUST reject with PROPOSAL_NOT_COMMITTED; the buyer finalizes first via get_products refine action 'finalize'." - ), - ] = None - total_budget: Annotated[ - TotalBudget | None, - Field( - description="Total budget for the media buy when executing a proposal. The publisher applies the proposal's allocation percentages to this amount to derive package budgets." - ), - ] = None - packages: Annotated[ - Sequence[Package] | None, - Field( - description="Array of package configurations. Required when not using proposal_id. When executing a proposal, this can be omitted and packages will be derived from the proposal's allocations.", - min_length=1, - ), - ] = None - brand: Annotated[ - Brand1, - Field( - description='Brand reference for this media buy. Resolved to full brand identity at execution time from brand.json or the registry.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - advertiser_industry: Annotated[ - AdvertiserIndustry | None, - Field( - description="Industry classification for this specific campaign. A brand may operate across multiple industries (brand.json industries field), but each media buy targets one. For example, a consumer health company running a wellness campaign sends 'healthcare.wellness', not 'cpg'. Sellers map this to platform-native codes (e.g., Spotify ADV categories, LinkedIn industry IDs). When omitted, sellers may infer from the brand manifest's industries field.", - title='Advertiser Industry', - ), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient | None, - Field( - description="Override the account's default billing entity for this specific buy. When provided, the seller invoices this entity instead. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request.", - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - io_acceptance: Annotated[ - IoAcceptance | None, - Field( - description="Acceptance of an insertion order from a committed proposal. Required when the proposal's insertion_order has requires_signature: true. References the io_id from the proposal's insertion_order." - ), - ] = None - po_number: Annotated[str | None, Field(description='Purchase order number for tracking')] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number. Primary financial reference for broadcast buys — links the order to the agency's media plan and billing system. Travels with the order and creative traffic identifiers through the transaction lifecycle.", - max_length=100, - ), - ] = None - start_time: Annotated[ - Literal['asap'] | AwareDatetime, - Field( - description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' - ), - ] - end_time: Annotated[ - AwareDatetime, Field(description='Campaign end date/time in ISO 8601 format') - ] - paused: Annotated[ - bool | None, - Field( - description="Create the media buy in a paused delivery state. When true, and the buy would otherwise be active because creatives are assigned and the flight has started, the seller returns media_buy_status 'paused'. Setup blockers still take precedence: a buy with no creatives remains 'pending_creatives', and a future-dated buy remains 'pending_start' until its flight can start. Defaults to false." - ), - ] = False - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async task status notifications. Publisher will send webhooks when status changes (working, input-required, completed, failed, canceled). Buyers SHOULD supply `push_notification_config.operation_id` as the canonical correlation value; publishers echo that field back verbatim in webhook payloads and MUST NOT parse the URL to derive it.', - title='Push Notification Config', - ), - ] = None - reporting_webhook: Annotated[ - ReportingWebhook | None, - Field( - description='Optional webhook configuration for automated reporting delivery', - title='Reporting Webhook', - ), - ] = None - artifact_webhook: Annotated[ - ArtifactWebhook | None, - Field( - description='Optional webhook configuration for content artifact delivery. Used by governance agents to validate content adjacency. Seller pushes artifacts to this endpoint; orchestrator forwards to governance agent for validation.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_response.py b/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_response.py deleted file mode 100644 index 7d6bf788..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/create_media_buy_response.py +++ /dev/null @@ -1,493 +0,0 @@ -# generated by datamodel-codegen: -# filename: create_media_buy_response.json -# timestamp: 2026-06-18T11:28:26+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class PostalCodeSystem(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class CreateMediaBuyResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_request.py b/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_request.py deleted file mode 100644 index 1d622bde..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_request.py +++ /dev/null @@ -1,915 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_media_buy_delivery_request.json -# timestamp: 2026-06-07T22:46:34+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class TimeGranularity(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class PostClick(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class PostView(PostClick): - pass - - -class Model(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class AttributionWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - PostClick | None, Field(description='Post-click attribution window to apply.') - ] = None - post_view: Annotated[ - PostView | None, Field(description='Post-view attribution window to apply.') - ] = None - model: Annotated[ - Model | None, - Field( - description='Attribution model to use. When omitted, the seller applies their default model.', - title='Attribution Model', - ), - ] = None - - -class GeoLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class PostalCodeSystem(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class SortMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - grps = 'grps' - reach = 'reach' - frequency = 'frequency' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - engagement_rate = 'engagement_rate' - cost_per_click = 'cost_per_click' - - -class StatusFilter(RootModel[list[MediaBuyStatus]]): - root: Annotated[ - list[MediaBuyStatus], - Field( - description='Filter by status. Can be a single status or array of statuses', - min_length=1, - ), - ] - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_level: Annotated[ - GeoLevel, - Field( - description='Geographic granularity level for the breakdown', - title='Geographic Targeting Level', - ), - ] - system: Annotated[ - MetroAreaSystem | PostalCodeSystem | LegacyPostalCodeSystem | None, - Field( - description="Optional classification system for metro or postal_area levels. Metro uses metro-system values (e.g., 'nielsen_dma'); native postal_area uses country-local postal-system values with country (e.g., country 'US', system 'zip'); deprecated legacy postal_area requests may use legacy-postal-system values such as 'us_zip'. Omit to request the level without selecting a specific system." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area requests; omitted for legacy postal_area and non-postal geo requests.', - pattern='^[A-Z]{2}$', - ), - ] = None - limit: Annotated[ - int | None, - Field( - description='Maximum number of geo entries to return. Defaults to 25. When truncated, by_geo_truncated is true in the response.', - ge=1, - ), - ] = 25 - sort_by: SortMetric | None = SortMetric.spend - - -class DeviceType(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - int | None, - Field( - description='Maximum number of entries to return. When omitted, all entries are returned (the enum is small and bounded).', - ge=1, - ), - ] = None - sort_by: SortMetric | None = SortMetric.spend - - -class DevicePlatform(DeviceType): - pass - - -class Audience(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - limit: Annotated[ - int | None, Field(description='Maximum number of entries to return. Defaults to 25.', ge=1) - ] = 25 - sort_by: SortMetric | None = SortMetric.spend - - -class Placement(Audience): - pass - - -class ReportingDimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo: Annotated[ - Geo | None, - Field( - description='Request geographic breakdown. Check reporting_capabilities.supports_geo_breakdown for available levels and systems.' - ), - ] = None - device_type: Annotated[ - DeviceType | None, Field(description='Request device type breakdown.') - ] = None - device_platform: Annotated[ - DevicePlatform | None, Field(description='Request device platform breakdown.') - ] = None - audience: Annotated[ - Audience | None, Field(description='Request audience segment breakdown.') - ] = None - placement: Annotated[Placement | None, Field(description='Request placement breakdown.')] = None - - -class GetMediaBuyDeliveryRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account1 | None, - Field( - description='Filter delivery data to a specific account. When omitted, returns data across all accessible accounts.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field(description='Array of media buy IDs to get delivery data for', min_length=1), - ] = None - status_filter: Annotated[ - MediaBuyStatus | StatusFilter | None, - Field(description='Filter by status. Can be a single status or array of statuses'), - ] = None - start_date: Annotated[ - str | None, - Field( - description="Start date for reporting period (YYYY-MM-DD). When omitted along with end_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - end_date: Annotated[ - str | None, - Field( - description="End date for reporting period (YYYY-MM-DD). When omitted along with start_date, returns campaign lifetime data. Only accepted when the product's reporting_capabilities.date_range_support is 'date_range'.", - pattern='^\\d{4}-\\d{2}-\\d{2}$', - ), - ] = None - include_package_daily_breakdown: Annotated[ - bool | None, - Field( - description='When true, include daily_breakdown arrays within each package in by_package. Useful for per-package pacing analysis and line-item monitoring. Omit or set false to reduce response size — package daily data can be large for multi-package buys over long flights.' - ), - ] = False - time_granularity: Annotated[ - TimeGranularity | None, - Field( - description="Per-window slice granularity for the pull, using the same vocabulary as reporting_webhook.reporting_frequency. When set, the seller returns per-window delivery slices over the date range — useful for reconstructing data a buyer's webhook receiver missed, since the slice payload is shape-aligned with what reporting_webhook would have delivered for the same window. Capability-scoped: the value MUST be one of the seller's declared reporting_capabilities.windowed_pull_granularities; otherwise the seller MUST return UNSUPPORTED_GRANULARITY. When omitted, behavior is unchanged (cumulative aggregates plus optional daily breakdowns per existing fields).", - title='Reporting Frequency', - ), - ] = None - include_window_breakdown: Annotated[ - bool | None, - Field( - description="When true, the response includes media_buy_deliveries[].windows[] — an array of per-window delivery slices over the date range at the requested time_granularity. Ignored when time_granularity is omitted. Each window's payload mirrors what reporting_webhook would have delivered for the same window, enabling lossless GET-path recovery for buyers who missed webhook fires. Omit or set false to reduce response size when only cumulative aggregates are needed." - ), - ] = False - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description='Attribution window to apply for conversion metrics. When provided, the seller returns conversion data using the requested lookback windows instead of their platform default. The seller echoes the applied window in the response. Sellers that do not support configurable windows ignore this field and return their default. Check get_adcp_capabilities conversion_tracking.attribution_windows for available options.' - ), - ] = None - reporting_dimensions: Annotated[ - ReportingDimensions | None, - Field( - description='Request dimensional breakdowns in delivery reporting. Each key enables a specific breakdown dimension within by_package — include as an empty object (e.g., "device_type": {}) to activate with defaults. Omit entirely for no breakdowns (backward compatible). Unsupported dimensions are silently omitted from the response. Note: keyword, catalog_item, and creative breakdowns are returned automatically when the seller supports them and are not controlled by this object.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_response.py b/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_response.py deleted file mode 100644 index ddbf5328..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buy_delivery_response.py +++ /dev/null @@ -1,12714 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_media_buy_delivery_response.json -# timestamp: 2026-06-18T11:30:14+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[ - AwareDatetime, - Field(description='ISO 8601 start timestamp in UTC (e.g., 2024-02-05T00:00:00Z)'), - ] - end: Annotated[ - AwareDatetime, - Field(description='ISO 8601 end timestamp in UTC (e.g., 2024-02-05T23:59:59Z)'), - ] - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class PostClick(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class PostView(PostClick): - pass - - -class Model(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class AttributionWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: Annotated[ - Model | None, - Field( - description="Attribution model used to assign credit when multiple touchpoints exist. SHOULD be populated when committing to a specific model; when absent, the seller's default applies.", - title='Attribution Model', - ), - ] = None - - -class AttributionWindow1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Status1(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - pending = 'pending' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - failed = 'failed' - reporting_delayed = 'reporting_delayed' - - -class Kind(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class Period(PostClick): - pass - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class QuartileData(AdCPBaseModel): - q1_views: Annotated[float | None, Field(description='25% completion views', ge=0.0)] = None - q2_views: Annotated[float | None, Field(description='50% completion views', ge=0.0)] = None - q3_views: Annotated[float | None, Field(description='75% completion views', ge=0.0)] = None - q4_views: Annotated[float | None, Field(description='100% completion views', ge=0.0)] = None - - -class VenueBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - venue_id: Annotated[str, Field(description='Venue identifier')] - venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None - venue_type: Annotated[ - str | None, - Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), - ] = None - impressions: Annotated[int, Field(description='Impressions delivered at this venue', ge=0)] - loop_plays: Annotated[int | None, Field(description='Loop plays at this venue', ge=0)] = None - screens_used: Annotated[ - int | None, Field(description='Number of screens used at this venue', ge=0) - ] = None - - -class DoohMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - loop_plays: Annotated[ - int | None, Field(description='Number of times ad played in rotation', ge=0) - ] = None - screens_used: Annotated[ - int | None, Field(description='Number of unique screens displaying the ad', ge=0) - ] = None - screen_time_seconds: Annotated[ - int | None, Field(description='Total display time in seconds', ge=0) - ] = None - sov_achieved: Annotated[ - float | None, - Field(description='Actual share of voice delivered (0.0 to 1.0)', ge=0.0, le=1.0), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." - ), - ] = None - venue_breakdown: Annotated[ - list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') - ] = None - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class Period1(PostClick): - pass - - -class ReachWindow1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period1 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics1(DoohMetrics): - pass - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class DeliveryStatus(StrEnum): - delivering = 'delivering' - completed = 'completed' - budget_exhausted = 'budget_exhausted' - flight_ended = 'flight_ended' - goal_met = 'goal_met' - - -class AttributionWindow2(AttributionWindow1): - pass - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class Period2(PostClick): - pass - - -class ReachWindow2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period2 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics2(DoohMetrics): - pass - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class ContentIdType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class Period3(PostClick): - pass - - -class ReachWindow3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period3 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics3(DoohMetrics): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class Period4(PostClick): - pass - - -class ReachWindow4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period4 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics4(DoohMetrics): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class Period5(PostClick): - pass - - -class ReachWindow5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period5 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics5(DoohMetrics): - pass - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class GeoLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class Period6(PostClick): - pass - - -class ReachWindow6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period6 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics6(DoohMetrics): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class Period7(PostClick): - pass - - -class ReachWindow7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period7 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics7(DoohMetrics): - pass - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class Period8(PostClick): - pass - - -class ReachWindow8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period8 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics8(DoohMetrics): - pass - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' - - -class Period9(PostClick): - pass - - -class ReachWindow9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period9 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics9(DoohMetrics): - pass - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class DailyBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - date: Annotated[str, Field(description='Date (YYYY-MM-DD)', pattern='^\\d{4}-\\d{2}-\\d{2}$')] - impressions: Annotated[float, Field(description='Daily impressions for this package', ge=0.0)] - spend: Annotated[float, Field(description='Daily spend for this package', ge=0.0)] - conversions: Annotated[ - float | None, Field(description='Daily conversions for this package', ge=0.0) - ] = None - conversion_value: Annotated[ - float | None, Field(description='Daily conversion value for this package', ge=0.0) - ] = None - roas: Annotated[ - float | None, - Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', - ge=0.0, - le=1.0, - ), - ] = None - - -class Period10(PostClick): - pass - - -class ReachWindow10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period10 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics10(DoohMetrics): - pass - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class Period11(PostClick): - pass - - -class ReachWindow11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period11 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics11(DoohMetrics): - pass - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent1): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent1): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class DailyBreakdownItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - date: Annotated[str, Field(description='Date (YYYY-MM-DD)', pattern='^\\d{4}-\\d{2}-\\d{2}$')] - impressions: Annotated[float, Field(description='Daily impressions', ge=0.0)] - spend: Annotated[float, Field(description='Daily spend', ge=0.0)] - conversions: Annotated[float | None, Field(description='Daily conversions', ge=0.0)] = None - conversion_value: Annotated[ - float | None, Field(description='Daily conversion value', ge=0.0) - ] = None - roas: Annotated[ - float | None, - Field(description='Daily return on ad spend (conversion_value / spend)', ge=0.0), - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Daily fraction of conversions from first-time brand buyers (0 = none, 1 = all)', - ge=0.0, - le=1.0, - ), - ] = None - - -class Issue1(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue1] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PricingModel(StrEnum): - cpm = 'cpm' - vcpm = 'vcpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpv = 'cpv' - cpp = 'cpp' - cpa = 'cpa' - flat_rate = 'flat_rate' - time = 'time' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow1 | None, - Field( - description="Attribution window for this outcome row. Object-valued duration (`{interval, unit}`), not a shorthand string. Outcome metrics measured over different windows represent the same metric over different time periods; the partition keeps them as separate rows so buyers don't accidentally aggregate.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class MetricAggregates1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description="Qualifier keys disambiguating this row from sibling rows under the same `metric_id`. Symmetric with `committed_metrics.qualifier` today; expected to diverge in future minors as transparency disclosures buyers don't commit to ship delivery-only. Closed (`additionalProperties: false`) — new qualifier keys ship explicitly." - ), - ] = None - value: Annotated[ - float, - Field( - description='Aggregated metric value for this `(metric_id, qualifier)` partition. Heterogeneous by `metric_id` — rate metrics (`viewable_rate`, `completion_rate`) are 0.0–1.0; cost-per metrics (`cost_per_acquisition`, `cost_per_completed_view`) are currency amounts; count metrics (`impressions`, `clicks`) are non-negative integers as numbers; ratio metrics (`roas`) are non-negative numbers. Buyer agents MUST inspect `metric_id` before doing arithmetic — same dispatch convention as `committed_metrics`.' - ), - ] - measurable_impressions: Annotated[ - float | None, - Field( - description='Coverage denominator for verification metrics (e.g., `viewable_rate`). Buyers compute coverage as `measurable_impressions / impressions` from the partition.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, Field(description='Component for `viewable_rate` (numerator).', ge=0.0) - ] = None - impressions: Annotated[ - float | None, - Field( - description='Component for rate metrics whose denominator is total impressions (e.g., `completion_rate`, `engagement_rate`).', - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, Field(description='Component for `completion_rate` (numerator).', ge=0.0) - ] = None - spend: Annotated[ - float | None, - Field( - description='Component for cost-per metrics (denominator-ish; the cost half of the ratio).', - ge=0.0, - ), - ] = None - conversions: Annotated[ - float | None, - Field(description='Component for `cost_per_acquisition` and ROAS-family metrics.', ge=0.0), - ] = None - conversion_value: Annotated[ - float | None, Field(description='Component for `roas` (numerator).', ge=0.0) - ] = None - clicks: Annotated[ - float | None, - Field(description='Component for `cost_per_click` and click-rate metrics.', ge=0.0), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class MetricAggregates2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor; metric definitions live on `get_adcp_capabilities.measurement.metrics[]`.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - qualifier: Annotated[ - dict[str, Any] | None, - Field( - description='Optional qualifier keys for vendor metrics that need disambiguation (rare today — most vendor methodologies are intrinsic to the metric definition).' - ), - ] = None - value: Annotated[ - float, - Field( - description="Aggregated vendor-attested value. Unit semantics defined by the vendor — see the vendor's measurement-agent metric definition." - ), - ] - measurable_impressions: Annotated[ - float | None, - Field( - description="Coverage denominator — vendor measurement is rarely 100% of delivery (only impressions where the vendor's SDK fired or panel matched). Buyers compute coverage as `measurable_impressions / impressions`. Same convention as `vendor_metric_value.measurable_impressions`.", - ge=0.0, - ), - ] = None - - -class MetricAggregates(RootModel[MetricAggregates1 | MetricAggregates2]): - root: Annotated[ - MetricAggregates1 | MetricAggregates2, - Field( - description='One cross-buy delivery aggregate partitioned by metric scope and qualifier. Row-symmetric with `package.committed_metrics` and delivery `missing_metrics` so buyers can reconcile by `(scope, metric_id, qualifier)`.', - discriminator='scope', - title='Delivery Metric Aggregate', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class AggregatedTotals(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impressions: Annotated[ - float, Field(description='Total impressions delivered across all media buys', ge=0.0) - ] - spend: Annotated[float, Field(description='Total amount spent across all media buys', ge=0.0)] - clicks: Annotated[ - float | None, - Field(description='Total clicks across all media buys (if applicable)', ge=0.0), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Total audio/video completions across all media buys (if applicable)', - ge=0.0, - ), - ] = None - views: Annotated[ - float | None, Field(description='Total views across all media buys (if applicable)', ge=0.0) - ] = None - conversions: Annotated[ - float | None, - Field(description='Total conversions across all media buys (if applicable)', ge=0.0), - ] = None - conversion_value: Annotated[ - float | None, - Field(description='Total conversion value across all media buys (if applicable)', ge=0.0), - ] = None - roas: Annotated[ - float | None, - Field( - description='Aggregate return on ad spend across all media buys (total conversion_value / total spend)', - ge=0.0, - ), - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of total conversions across all media buys from first-time brand buyers (weighted by conversion volume, not a simple average of per-buy rates)', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_acquisition: Annotated[ - float | None, - Field( - description='Aggregate cost per conversion across all media buys (total spend / total conversions)', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field( - description='Aggregate completion rate across all media buys (weighted by impressions, not a simple average of per-buy rates)', - ge=0.0, - le=1.0, - ), - ] = None - reach: Annotated[ - float | None, - Field( - description='Deduplicated reach across all media buys (if the seller can deduplicate across buys; otherwise sum of per-buy reach). Only present when all media buys share the same reach_unit. Omitted when reach units are heterogeneous — use per-buy reach values instead.', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for reach. Only present when all aggregated media buys use the same reach_unit.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description='Average frequency per reach unit across all media buys (impressions / reach when cross-buy deduplication is available). Only present when reach is present.', - ge=0.0, - ), - ] = None - media_buy_count: Annotated[ - int, Field(description='Number of media buys included in the response', ge=0) - ] - metric_aggregates: Annotated[ - list[MetricAggregates] | None, - Field( - description="Cross-buy delivery aggregates partitioned by qualifier. Row-symmetric with `package.committed_metrics` and `by_package[].missing_metrics` — same atomic unit `(scope, metric_id, qualifier)` — so reconciliation collapses to a row-level join on the tuple. Granularity rule: one row per `(metric_id, full-qualifier-set)`, reported at the finest available granularity; buyers re-aggregate up if they want a coarser view. Used only for metrics with non-empty qualifier sets — unqualified metrics (`impressions`, `spend`, `media_buy_count`, etc.) remain at the top of `aggregated_totals`. **Mutual exclusion MUST**: for any `metric_id` appearing in `metric_aggregates`, the corresponding top-level scalar in `aggregated_totals` MUST be omitted (not zeroed) — avoids duplicate sources of truth. The qualifier vocabulary on this delivery surface is closed today (`additionalProperties: false`, same content as `committed_metrics.qualifier`) but is expected to **diverge from contract qualifier in future minors** as transparency disclosures buyers don't commit to ship delivery-only (e.g., `tracker_firing` pending #3832 resolution). Each row carries a `value` plus inlined per-metric component fields (e.g., `measurable_impressions` and `viewable_impressions` for `viewable_rate`; `spend` and `conversions` for `cost_per_acquisition`). Per-buy `totals` keeps its flat shape — each buy is single-qualifier by definition; only the aggregate spans qualifiers. **Qualifier-set drift across reports**: when a campaign gains a new qualifier mid-flight (e.g., adds `tracker_firing` partitioning in week 2), prior periods' rows remain valid at their original granularity; buyers SHOULD NOT retroactively repartition.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'value': 0.7286, - 'measurable_impressions': 700000, - 'viewable_impressions': 510000, - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'groupm'}, - 'value': 0.55, - 'measurable_impressions': 180000, - 'viewable_impressions': 99000, - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'qualifier': {}, - 'value': 4.2, - 'measurable_impressions': 800000, - }, - ] - ], - ), - ] = None - - -class ByEventTypeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: EventType - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[float, Field(description='Number of events of this type', ge=0.0)] - value: Annotated[ - float | None, Field(description='Total monetary value of events of this type', ge=0.0) - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor1 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class ByActionSourceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_source: ActionSource - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[ - float, Field(description='Number of conversions from this action source', ge=0.0) - ] - value: Annotated[ - float | None, - Field(description='Total monetary value of conversions from this action source', ge=0.0), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor2, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Totals(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - effective_rate: Annotated[ - float | None, - Field( - description="Effective rate paid per unit based on pricing_model (e.g., actual CPM for 'cpm', actual cost per completed view for 'cpcv', actual cost per point for 'cpp')", - ge=0.0, - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor3 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo4 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride4 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor4, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Qualifier1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow2 | None, - Field( - description="A time duration expressed as an interval and unit. Used for frequency cap windows, attribution windows, reach optimization windows, time budgets, and other time-based settings. When unit is 'campaign', interval must be 1 — the window spans the full campaign flight.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class MissingMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['standard'] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier1 | None, - Field( - description='Mirrors the qualifier on `committed_metrics` so the missing entry preserves the contract distinction (e.g., flagging MRC viewability as missing when only GroupM was reported, vendor-attested completion as missing when only seller-attested was reported, or deterministic_purchase attribution as missing when only probabilistic was reported). MUST match the qualifier on the corresponding `committed_metrics` entry the missing flag refers to.' - ), - ] = None - - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo5 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride5 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class MissingMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Literal['vendor'] = 'vendor' - vendor: Annotated[ - Vendor5, - Field( - description='Reference to a brand by domain and optional brand_id. The domain hosts /.well-known/brand.json or is registered in the brand registry. For single-brand domains, brand_id can be omitted. For house-of-brands domains, brand_id identifies the specific brand.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for a vendor-defined metric within the vendor's vocabulary. Stable lookup key; the vendor publishes the canonical list (with category, methodology, and standard alignment) in `brand.json` `agents[type='measurement']`. Lowercase with underscores so a future enum promotion into `available-metric.json` is a literal string lift. Identifier is namespaced by the vendor — the same `metric_id` may mean different things in different vendors' vocabularies.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class MissingMetrics(RootModel[MissingMetrics1 | MissingMetrics2]): - root: Annotated[ - MissingMetrics1 | MissingMetrics2, - Field( - description='One metric the binding reporting contract declared but that is not populated in a delivery report. Symmetric with `committed_metrics` and discriminated by `scope`.', - discriminator='scope', - title='Missing Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo6 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride6 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor6 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo7 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride7 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor7, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByCatalogItemItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow2 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics2 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability2 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue2] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - content_id: Annotated[ - str, Field(description='Catalog item identifier (e.g., SKU, GTIN, job_id, offering_id)') - ] - content_id_type: Annotated[ - ContentIdType | None, - Field(description='Identifier type for this content_id', title='Content ID Type'), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo8 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride8 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor8 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo9 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride9 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor9, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByCreativeItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow3 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics3 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability3 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue3] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - creative_id: Annotated[ - str, Field(description='Creative identifier matching the creative assignment') - ] - weight: Annotated[ - float | None, - Field( - description='Observed delivery share for this creative within the package during the reporting period, expressed as a percentage (0-100). Reflects actual delivery distribution, not a configured setting.', - ge=0.0, - le=100.0, - ), - ] = None - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo10 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride10 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor10 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo11 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride11 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor11, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByKeywordItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow4 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics4 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability4 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue4] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - keyword: Annotated[str, Field(description='The targeted keyword')] - match_type: Annotated[ - MatchType, - Field( - description='Keyword targeting match type. broad: ads may serve on queries semantically related to the keyword. phrase: ads serve when the query contains the keyword phrase. exact: ads serve only when the query matches the keyword exactly.', - title='Match Type', - ), - ] - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo12 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride12 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor12 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo13 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride13 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor13, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByGeoItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow5 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics5 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability5 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue5] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - geo_level: Annotated[ - GeoLevel, - Field( - description='Geographic level of this entry (country, region, metro, postal_area)', - title='Geographic Targeting Level', - ), - ] - system: Annotated[ - str | None, - Field( - description='Classification system for metro or postal_area levels. Metro rows use metro-system values. Native postal rows use country-local postal-system values with country; deprecated legacy postal rows may use legacy-postal-system values.' - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code for native postal_area rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')" - ), - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo14 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride14 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor14 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo15 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride15 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor15, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByDeviceTypeItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow6 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics6 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability6 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue6] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - device_type: Annotated[ - DeviceType, - Field( - description='Device form factor (desktop, mobile, tablet, ctv, dooh, unknown)', - title='Device Type', - ), - ] - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction16(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo16 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride16 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor16 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo17 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride17 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor17, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByDevicePlatformItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow7 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics7 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability7 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue7] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - device_platform: Annotated[ - DevicePlatform, Field(description='Operating system platform', title='Device Platform') - ] - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction18(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo18 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride18 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor18 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo19 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride19 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor19, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByAudienceItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow8 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics8 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability8 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue8] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - audience_id: Annotated[ - str, - Field( - description="Audience segment identifier. For 'synced' source, matches audience_id from sync_audiences. For other sources, seller-defined." - ), - ] - audience_source: Annotated[ - AudienceSource, - Field( - description='Origin of the audience segment (synced, platform, third_party, lookalike, retargeting, unknown)', - title='Audience Source', - ), - ] - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name') - ] = None - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo20 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride20 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor20 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo21 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor21(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride21 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor21, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByPlacementItem(AdCPBaseModel): - impressions: Annotated[float, Field(description='Impressions delivered', ge=0.0)] - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow9 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics9 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability9 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue9] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - placement_id: Annotated[ - str, Field(description="Placement identifier from the product's placements array") - ] - placement_name: Annotated[str | None, Field(description='Human-readable placement name')] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Canonical publisher domain whose adagents.json namespace this placement belongs to, matching the `publisher_domain` on the product's `placements[]` entry that `placement_id` resolves to (see core/placement.json). Lets buyers attribute delivered impressions to a publisher namespace without re-fetching the product catalog — the common case for multi-publisher buys through a single sales agent. Sellers SHOULD emit it whenever the resolving product placement carries a publisher_domain (always true for `kind: publisher_ref`); sellers MAY omit it only for `seller_inline` placements in a legacy single-publisher context where the seller agent's own domain is the namespace. Single-valued: a placement resolves within exactly one publisher namespace (package-level attribution, where an ad-network product can span publishers, is a separate concern).", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - - -class ByPackageItem(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow1 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics1 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability1 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue1] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - package_id: Annotated[str, Field(description="Seller's package identifier")] - pacing_index: Annotated[ - float | None, - Field(description='Delivery pace (1.0 = on track, <1.0 = behind, >1.0 = ahead)', ge=0.0), - ] = None - pricing_model: PricingModel - rate: Annotated[ - float, - Field( - description='The pricing rate for this package in the specified currency. For fixed-rate pricing, this is the agreed rate (e.g., CPM rate of 12.50 means $12.50 per 1,000 impressions). For auction-based pricing, this represents the effective rate based on actual delivery.', - ge=0.0, - ), - ] - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code (e.g., USD, EUR, GBP) for this package's pricing. Indicates the currency in which the rate and spend values are denominated. Different packages can use different currencies when supported by the publisher.", - pattern='^[A-Z]{3}$', - ), - ] - delivery_status: Annotated[ - DeliveryStatus | None, - Field( - description='System-reported operational state of this package. Reflects actual delivery state independent of buyer pause control.' - ), - ] = None - paused: Annotated[ - bool | None, Field(description='Whether this package is currently paused by the buyer') - ] = None - is_final: Annotated[ - bool | None, - Field( - description="Whether this delivery data is final for the reporting period. When false, the data may be updated as measurement matures (e.g., broadcast C7 window accumulating DVR playback) or as processing completes (e.g., IVT filtering, deduplication). When true, the seller considers this data closed — no further updates for this period — and is willing to invoice on it subject to the buy's `measurement_terms.billing_measurement`. Absent means the seller does not distinguish provisional from final data." - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp at which this package's data became final. Present only when `is_final: true`. Anchors reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement.measurement_window`." - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement window this data represents, referencing a window_id from the product's reporting_capabilities.measurement_windows. For broadcast: 'live', 'c3', 'c7'. When absent, the data is not windowed (standard digital reporting). When present with is_final: false, a later report for the same period will provide a wider window or more complete data.", - examples=['live', 'c3', 'c7'], - max_length=50, - ), - ] = None - supersedes_window: Annotated[ - str | None, - Field( - description="Which measurement window this data replaces. Present on window_update notifications to indicate progression (e.g., 'live' when reporting C3 data that supersedes live-only numbers). Absent on the first report for a period. Buyers should replace stored data for the superseded window with this report's data.", - examples=['live', 'c3'], - max_length=50, - ), - ] = None - missing_metrics: Annotated[ - list[MissingMetrics] | None, - Field( - description="Metrics that the binding reporting contract declared but that are NOT populated in this report. Reconciliation source: when `package.committed_metrics` is present, `missing_metrics` is computed against entries where `committed_at < reporting_period.end` — independent of subsequent product mutations and respecting the commitment timestamp on each entry (a metric committed mid-flight is only flagged missing in reports for periods after its commitment). When `package.committed_metrics` is absent, fall back to the product's current `reporting_capabilities.available_metrics` (no timestamp filter). Empty array (or absent) indicates clean delivery against the contract. Non-empty signals an accountability breach — the seller committed to the metric but did not produce the value here. Sellers MUST exclude metrics that are not yet measurable for the current `measurement_window` (e.g., post-IVT counts during the live window) — those will appear (or not) when a wider window supersedes this report via `supersedes_window`. Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. Symmetric with `committed_metrics`.", - examples=[ - [], - [{'scope': 'standard', 'metric_id': 'completed_views'}], - [ - {'scope': 'standard', 'metric_id': 'completed_views'}, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - }, - ], - ], - ), - ] = None - by_catalog_item: Annotated[ - list[ByCatalogItemItem] | None, - Field( - description='Delivery by catalog item within this package. Available for catalog-driven packages when the seller supports item-level reporting.' - ), - ] = None - by_creative: Annotated[ - list[ByCreativeItem] | None, - Field( - description='Metrics broken down by creative within this package. Available when the seller supports creative-level reporting.' - ), - ] = None - by_keyword: Annotated[ - list[ByKeywordItem] | None, - Field( - description='Metrics broken down by keyword within this package. One row per (keyword, match_type) pair — the same keyword with different match types appears as separate rows. Keyword-grain only: rows reflect aggregate performance of each targeted keyword, not individual search queries. Rows may not sum to package totals when a single impression is attributed to the triggering keyword only. Available for search and retail media packages when the seller supports keyword-level reporting.' - ), - ] = None - by_geo: Annotated[ - list[ByGeoItem] | None, - Field( - description="Delivery by geographic area within this package. Available when the buyer requests geo breakdown via reporting_dimensions and the seller supports it. Each dimension's rows are independent slices that should sum to the package total." - ), - ] = None - by_geo_truncated: Annotated[ - bool | None, - Field( - description='Whether by_geo was truncated due to the requested limit or a seller-imposed maximum. Sellers MUST return this flag whenever by_geo is present (false means the list is complete).' - ), - ] = None - by_device_type: Annotated[ - list[ByDeviceTypeItem] | None, - Field( - description='Delivery by device form factor within this package. Available when the buyer requests device_type breakdown via reporting_dimensions and the seller supports it.' - ), - ] = None - by_device_type_truncated: Annotated[ - bool | None, - Field( - description='Whether by_device_type was truncated. Sellers MUST return this flag whenever by_device_type is present (false means the list is complete).' - ), - ] = None - by_device_platform: Annotated[ - list[ByDevicePlatformItem] | None, - Field( - description='Delivery by operating system within this package. Available when the buyer requests device_platform breakdown via reporting_dimensions and the seller supports it. Useful for CTV campaigns where tvOS vs Roku OS vs Fire OS matters.' - ), - ] = None - by_device_platform_truncated: Annotated[ - bool | None, - Field( - description='Whether by_device_platform was truncated. Sellers MUST return this flag whenever by_device_platform is present (false means the list is complete).' - ), - ] = None - by_audience: Annotated[ - list[ByAudienceItem] | None, - Field( - description="Delivery by audience segment within this package. Available when the buyer requests audience breakdown via reporting_dimensions and the seller supports it. Only 'synced' audiences are directly targetable via the targeting overlay; other sources are informational." - ), - ] = None - by_audience_truncated: Annotated[ - bool | None, - Field( - description='Whether by_audience was truncated. Sellers MUST return this flag whenever by_audience is present (false means the list is complete).' - ), - ] = None - by_placement: Annotated[ - list[ByPlacementItem] | None, - Field( - description="Delivery by placement within this package. Available when the buyer requests placement breakdown via reporting_dimensions and the seller supports it. Placement IDs reference the product's placements array." - ), - ] = None - by_placement_truncated: Annotated[ - bool | None, - Field( - description='Whether by_placement was truncated. Sellers MUST return this flag whenever by_placement is present (false means the list is complete).' - ), - ] = None - daily_breakdown: Annotated[ - list[DailyBreakdownItem] | None, - Field( - description='Day-by-day delivery for this package. Only present when include_package_daily_breakdown is true in the request. Enables per-package pacing analysis and line-item monitoring.' - ), - ] = None - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo22 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride22 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor22 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo23 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride23 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor23, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Totals1(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float | None, Field(description='Amount spent', ge=0.0)] = None - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow10 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics10 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability10 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue10] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo24 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor24(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride24 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor24 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo25 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor25(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride25 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor25, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByPackageItem1(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float | None, Field(description='Amount spent', ge=0.0)] = None - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow11 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics11 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability11 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue11] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - package_id: Annotated[str, Field(description="Seller's package identifier")] - - -class Window(AdCPBaseModel): - window_start: Annotated[ - AwareDatetime, Field(description='ISO 8601 start of the window slice (inclusive). UTC.') - ] - window_end: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 end of the window slice (exclusive). The next row's window_start equals this value when slices are contiguous." - ), - ] - totals: Annotated[ - Totals1, - Field( - description='Aggregate metrics for this window slice across all packages. Shape-aligned with reporting_webhook fire payloads at the same granularity.' - ), - ] - by_package: Annotated[ - list[ByPackageItem1] | None, - Field( - description='Per-package metrics for this window slice. Same shape as the parent media_buy_deliveries[].by_package row but scoped to the window. Sellers MAY omit when per-package window-level data is unavailable; when present, package_id values MUST match the parent by_package entries.' - ), - ] = None - is_final: Annotated[ - bool | None, - Field( - description='Whether the slice data is final for this window. Same semantics as the per-package is_final flag — when false, a later read may return revised numbers for the same window as measurement matures (broadcast C3 → C7, post-IVT filtering, etc.).' - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement-window stage this slice represents (e.g., 'live', 'c3', 'c7'), referencing a window_id from the product's reporting_capabilities.measurement_windows. Absent when the data is not phased.", - max_length=50, - ), - ] = None - - -class MediaBuyDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier")] - status: Annotated[ - Status1, - Field( - description='Current media buy status. Lifecycle states use the same taxonomy as media-buy-status (`pending_creatives`, `pending_start`, `active`, `paused`, `completed`, `rejected`, `canceled`). In webhook context, reporting_delayed indicates data temporarily unavailable. `pending` is accepted as a legacy alias for pending_start.' - ), - ] - expected_availability: Annotated[ - AwareDatetime | None, - Field( - description='When delayed data is expected to be available (only present when status is reporting_delayed)' - ), - ] = None - is_adjusted: Annotated[ - bool | None, - Field( - description='Indicates this delivery contains updated data for a previously reported period. Buyer should replace previous period data with these totals.' - ), - ] = None - is_final: Annotated[ - bool | None, - Field( - description="Whether this row's delivery data is final for the reporting period. The row does not carry its own `measurement_window` — that lives on each `by_package[*]` entry. Reconciliation joins on per-package `measurement_window`; this row-level flag is a convenience roll-up. Sellers MUST NOT emit `is_final: true` at the row level unless every entry in `by_package` has `is_final: true` for the same `measurement_window` as the buy's `measurement_terms.billing_measurement.measurement_window` (or for the row's natural window when no `billing_measurement.measurement_window` is set). On any disagreement between row-level and package-level finality, package-level is authoritative. When true, the seller considers these numbers closed and is willing to invoice on them subject to `measurement_terms.billing_measurement`. When false, numbers may still move as measurement matures (broadcast C3 → C7) or processing completes (IVT scrubbing, dedup). When absent, the seller does not distinguish provisional from final at the row level — consult per-package `is_final`." - ), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp at which this row became final. Present only when `is_final: true`. Anchors the buyer's reconciliation and (when later defined) dispute-window clocks against the buy's `measurement_terms.billing_measurement`. Computed as the latest `finalized_at` across the row's packages for the reconciliation window." - ), - ] = None - pricing_model: PricingModel | None = None - totals: Totals - by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package')] - windows: Annotated[ - list[Window] | None, - Field( - description="Per-window delivery slices over the reporting period at the requested time_granularity. Only present when the request set time_granularity and include_window_breakdown: true. Each slice mirrors what reporting_webhook would have delivered for the same window — buyers who missed webhook fires can reconstruct identical data by reading this array. Slice rows are ordered by window_start ascending; consecutive rows are contiguous (each row's window_end equals the next row's window_start) and partition the requested date range at the chosen granularity. Sellers MUST exclude this field when time_granularity is omitted; when set, sellers MUST honor pulls at any granularity in reporting_capabilities.windowed_pull_granularities (otherwise return UNSUPPORTED_GRANULARITY). See snapshot-and-log Rule 4 for the two-paths-parity contract this surface anchors." - ), - ] = None - daily_breakdown: Annotated[ - list[DailyBreakdownItem1] | None, Field(description='Day-by-day delivery') - ] = None - - -class GetMediaBuyDeliveryResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - notification_type: Annotated[ - NotificationType | None, - Field( - description='Type of webhook notification (only present in webhook deliveries): scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = resending period with corrected data (same window), window_update = resending period with a wider measurement window (e.g., C3 superseding live, C7 superseding C3)' - ), - ] = None - partial_data: Annotated[ - bool | None, - Field( - description='Indicates if any media buys in this webhook have missing/delayed data (only present in webhook deliveries)' - ), - ] = None - unavailable_count: Annotated[ - int | None, - Field( - description='Number of media buys with reporting_delayed or failed status (only present in webhook deliveries when partial_data is true)', - ge=0, - ), - ] = None - sequence_number: Annotated[ - int | None, - Field( - description='Sequential notification number (only present in webhook deliveries, starts at 1)', - ge=1, - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for next expected notification (only present in webhook deliveries when notification_type is not 'final')" - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, - Field(description='Date range for the report. All periods use UTC timezone.'), - ] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description='Attribution methodology and lookback windows used for conversion metrics in this response. All media buys from a single seller share the same attribution methodology. Enables cross-platform comparison (e.g., Amazon 14-day click vs. Criteo 30-day click).', - title='Attribution Window', - ), - ] = None - aggregated_totals: Annotated[ - AggregatedTotals | None, - Field( - description='Combined metrics across all returned media buys. Only included in API responses (get_media_buy_delivery), not in webhook notifications.' - ), - ] = None - media_buy_deliveries: Annotated[ - Sequence[MediaBuyDelivery], - Field( - description='Array of delivery data for media buys. When used in webhook notifications, may contain multiple media buys aggregated by publisher. When used in get_media_buy_delivery API responses, typically contains requested media buys.' - ), - ] - errors: Annotated[ - list[Error] | None, - Field( - description='Task-specific errors and warnings (e.g., missing delivery data, reporting platform issues)' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_request.py b/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_request.py deleted file mode 100644 index 57b45951..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_request.py +++ /dev/null @@ -1,698 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_media_buys_request.json -# timestamp: 2026-05-28T10:36:07+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class StatusFilter(RootModel[list[MediaBuyStatus]]): - root: Annotated[ - list[MediaBuyStatus], - Field( - description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.', - min_length=1, - ), - ] - - -class GetMediaBuysRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account1 | None, - Field( - description='Account to retrieve media buys for. When omitted, returns data across all accessible accounts.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - media_buy_ids: Annotated[ - list[str] | None, - Field( - description='Array of media buy IDs to retrieve. When omitted, returns a paginated set of accessible media buys matching status_filter.', - min_length=1, - ), - ] = None - status_filter: Annotated[ - MediaBuyStatus | StatusFilter | None, - Field( - description='Filter by status. Can be a single status or array of statuses. Defaults to ["active"] when media_buy_ids is omitted. When media_buy_ids is provided, no implicit status filter is applied.' - ), - ] = None - include_snapshot: Annotated[ - bool | None, - Field( - description='When true, include a near-real-time delivery snapshot for each package. Snapshots reflect the latest available entity-level stats from the platform (e.g., updated every ~15 minutes on GAM, ~1 hour on batch-only platforms). The staleness_seconds field on each snapshot indicates data freshness. If a snapshot cannot be returned, package.snapshot_unavailable_reason explains why. Defaults to false.' - ), - ] = False - include_history: Annotated[ - int | None, - Field( - description='When present, include the last N revision history entries for each media buy (returns min(N, available entries)). Each entry contains revision number, timestamp, actor, and a summary of what changed. Omit or set to 0 to exclude history (default). Recommended: 5-10 for monitoring, 50+ for audit.', - ge=0, - le=1000, - ), - ] = 0 - include_webhook_activity: Annotated[ - bool | None, - Field( - description="When true, each returned media buy includes a `webhook_activity` array describing recent delivery-report webhook fires for the calling principal. Used by buyer agents to verify whether a publisher actually fired against the buyer's registered endpoint and what the endpoint returned — closes the operator-ticket loop for webhook debugging. Scoped to the calling principal: a buyer sees only fires targeting its own endpoint, even when multiple principals share visibility into the same media buy. Defaults to false. See `webhook_activity_limit` for the per-buy cap." - ), - ] = False - webhook_activity_limit: Annotated[ - int | None, - Field( - description="Maximum number of webhook delivery records to return per media buy, ordered most-recent first. Ignored when `include_webhook_activity` is false. Sellers that surface webhook activity MUST retain records for at least 30 days from each record's `completed_at` (see `webhook_activity` description in the response schema for the `pending`-status carve-out); sellers unable to honor that floor MUST omit the field entirely rather than truncate. When a buy has more historical fires than the limit, only the most recent are returned — there is no cursor for older fires; this surface is a debug aid, not a full audit log.", - ge=1, - le=200, - ), - ] = 50 - pagination: Annotated[ - Pagination | None, - Field( - description='Cursor-based pagination controls. Strongly recommended when querying broad scopes (for example, all active media buys in an account).', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_response.py b/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_response.py deleted file mode 100644 index bc320b71..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_media_buys_response.py +++ /dev/null @@ -1,4233 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_media_buys_response.json -# timestamp: 2026-06-18T11:30:20+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status1(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Billing(StrEnum): - operator = 'operator' - agent = 'agent' - advertiser = 'advertiser' - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role1(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role1, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class BillingEntity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PaymentTerms(StrEnum): - net_15 = 'net_15' - net_30 = 'net_30' - net_45 = 'net_45' - net_60 = 'net_60' - net_90 = 'net_90' - prepay = 'prepay' - - -class CreditLimit(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL where the human can complete the required action (credit application, legal agreement, add funds).' - ), - ] = None - message: Annotated[str, Field(description="Human-readable description of what's needed.")] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When this setup link expires.') - ] = None - - -class AccountScope(StrEnum): - operator = 'operator' - brand = 'brand' - operator_brand = 'operator_brand' - agent = 'agent' - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - - -class Protocol(StrEnum): - s3 = 's3' - gcs = 'gcs' - azure_blob = 'azure_blob' - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class Compression(StrEnum): - gzip = 'gzip' - none = 'none' - - -class ReportingBucket(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protocol: Annotated[ - Protocol, Field(description='Cloud storage protocol', title='Cloud Storage Protocol') - ] - bucket: Annotated[ - str, - Field( - description='Bucket or container name', - max_length=63, - min_length=3, - pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', - ), - ] - prefix: Annotated[ - str | None, - Field( - description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', - examples=['accounts/pinnacle/adcp', 'reporting/2024'], - max_length=512, - pattern='^[a-zA-Z0-9/_.-]+$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description='Cloud region for the bucket', - examples=['us-east-1', 'europe-west1'], - max_length=64, - pattern='^[a-z0-9-]+$', - ), - ] = None - format: Annotated[ - Format | None, - Field( - description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' - ), - ] = Format.jsonl - compression: Annotated[ - Compression | None, Field(description='Compression applied to delivered files') - ] = Compression.gzip - file_retention_days: Annotated[ - int, - Field( - description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', - examples=[14, 30, 90], - ge=1, - ), - ] - setup_instructions: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' - ), - ] = None - - -class Contact1(Contact): - pass - - -class InvoiceRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact1] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Status2(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class Health(AdCPBaseModel): - pass - - -class ResourceType(StrEnum): - audience = 'audience' - creative = 'creative' - catalog_item = 'catalog_item' - event_source = 'event_source' - property = 'property' - - -class To(StrEnum): - suspended = 'suspended' - rejected = 'rejected' - withdrawn = 'withdrawn' - insufficient = 'insufficient' - depublished = 'depublished' - - -class Transition(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - from_: Annotated[ - str | None, - Field( - alias='from', - description="Prior status of the resource (e.g., 'ready', 'approved', 'good'). Optional — sellers SHOULD include when known, MAY omit when the resource was discovered already in an offline state (e.g., a property depublished via brand.json crawl with no prior snapshot). Open string at the schema layer because each resource_type has its own serviceable-state vocabulary; the pattern constraint blocks free-form garbage, and the impairment.coherence assertion validates that 'from' is a known serviceable value for the resource_type.", - pattern='^[a-z][a-z0-9_]*$', - ), - ] = None - to: Annotated[ - To, - Field( - description="Current (offline) status of the resource. Drawn from the resource_type's canonical lifecycle enum; see impairment-offline-state for per-value resource_type pairing. The pairing is validated by impairment.coherence.", - title='Impairment Offline State', - ), - ] - - -class ReasonCode(StrEnum): - policy_violation = 'policy_violation' - consent_expired = 'consent_expired' - ttl_expired = 'ttl_expired' - pii_audit_failed = 'pii_audit_failed' - seller_removed = 'seller_removed' - content_rejected = 'content_rejected' - identity_authorization_revoked = 'identity_authorization_revoked' - identity_authorization_expired = 'identity_authorization_expired' - source_private = 'source_private' - source_offline = 'source_offline' - property_depublished = 'property_depublished' - - -class Impairment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - impairment_id: Annotated[ - str, - Field( - description="Stable identifier for this impairment, used as the notification_id when the impairment fires via webhook. Stable across re-emissions of the same open impairment (e.g., the seller re-fires after the buyer's receiver was down) and across the closing fire that signals resolution. A new impairment for the same resource_id after closure receives a new impairment_id. Distinct from the per-fire idempotency_key issued at the webhook transport layer — see snapshot-and-log Rule 1. Receivers correlate webhook fires to current impairments[] state by impairment_id; receivers suppress duplicate transport-layer retries by idempotency_key. Seeing the same impairment_id with different idempotency_keys is a re-emission signal, not a retry — the buyer should treat it as a notice that something may have been missed." - ), - ] - resource_type: Annotated[ - ResourceType, - Field( - description="The kind of upstream dependency that transitioned to an offline state. Values are drawn from the x-entity vocabulary (see core/x-entity-types.json) and identify a buyer-referenced object with its own lifecycle that the seller can take offline. This is the subset of x-entity types for which a media buy's serving depends on the resource — not a new typology, just the impairment-relevant slice." - ), - ] - resource_id: Annotated[ - str, - Field( - description="Seller's identifier for the specific resource that transitioned. References the same id space as the corresponding sync_/list_ task responses (e.g., audience_id, creative_id)." - ), - ] - package_ids: Annotated[ - list[str], - Field( - description="Packages within this media buy whose delivery is degraded by the impairment. MUST list at least one package — cosmetic effects that do not degrade any package's ability to serve MUST NOT be reported as impairments.", - min_length=1, - ), - ] - transition: Annotated[ - Transition, - Field(description='The resource-level status transition that triggered this impairment.'), - ] - reason_code: Annotated[ - ReasonCode, - Field( - description='Categorical reason for the offline transition. Drives buyer-side remediation logic.', - title='Impairment Reason Code', - ), - ] - reason: Annotated[ - str | None, - Field( - description='Human-readable explanation. Supplements reason_code with seller-specific detail.', - max_length=500, - ), - ] = None - observed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller observed the resource transition to its offline state.' - ), - ] - remediation: Annotated[ - str | None, - Field( - description='Action the buyer can take to clear the impairment, if any. Free text. Absent when no buyer-side remediation is possible (e.g., seller-initiated withdrawal pending re-publication).', - max_length=500, - ), - ] = None - - -class Mode(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - requires_approval = 'requires_approval' - - -class Sla(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - response_max: Annotated[ - str | None, - Field( - description='Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration.', - examples=['PT5M', 'PT4H', 'P1D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - completion_max: Annotated[ - str | None, - Field( - description='Maximum time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, approval resolved). ISO 8601 duration.', - examples=['PT1H', 'PT24H', 'P2D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - - -class Status3(StrEnum): - success = 'success' - failed = 'failed' - timeout = 'timeout' - connection_error = 'connection_error' - pending = 'pending' - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - revision: Annotated[ - int, Field(description='Revision number after this change was applied.', ge=1) - ] - timestamp: Annotated[AwareDatetime, Field(description='When this change occurred.')] - actor: Annotated[ - str | None, - Field( - description='Identity of who made the change — derived from authentication context, not caller-provided. Format is seller-defined (e.g., agent URL, user email, API key label).' - ), - ] = None - action: Annotated[ - str, - Field( - description='What happened. Standard actions: created, activated, paused, resumed, canceled, rejected, completed, updated_budget, updated_dates, updated_packages, package_canceled, package_paused, package_resumed. Sellers MAY use additional platform-specific actions (e.g., creative_approved, targeting_updated) — use ext on the history entry for structured metadata about custom actions.' - ), - ] - summary: Annotated[ - str | None, - Field( - description="Human-readable summary of the change (e.g., 'Budget increased from $5,000 to $7,500 on pkg_abc').", - max_length=500, - ), - ] = None - package_id: Annotated[ - str | None, - Field(description='Package affected, when the change targeted a specific package.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatOptionRefs1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRefs2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class FormatOptionRefs(RootModel[FormatOptionRefs1 | FormatOptionRefs2]): - root: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class GeoCountry(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas4(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude1(GeoPostalAreas1): - pass - - -class GeoPostalAreasExclude2(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude3(GeoPostalAreas3): - pass - - -class GeoPostalAreasExclude4(GeoPostalAreas4): - pass - - -class GeoPostalAreasExclude5(GeoPostalAreas5): - pass - - -class GeoPostalAreasExclude6(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude7(GeoPostalAreas7): - pass - - -class GeoPostalAreasExclude8(GeoPostalAreas8): - pass - - -class GeoPostalAreasExclude9(GeoPostalAreas9): - pass - - -class GeoPostalAreasExclude10(GeoPostalAreas10): - pass - - -class Day(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[Day], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Signal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Signal4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal5(Signal1, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal6(Signal2, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal7(Signal3, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal5 | Signal6 | Signal7]): - root: Annotated[ - Signal5 | Signal6 | Signal7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting(RootModel[SignalTargeting1 | SignalTargeting2 | SignalTargeting3]): - root: Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Suppress(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class Per(Health): - pass - - -class Window(Suppress): - pass - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - Per | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class AcceptedMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AcceptedMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class DevicePlatformEnum(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Unit2(StrEnum): - min = 'min' - hr = 'hr' - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: Annotated[ - Unit2, - Field( - description='Time unit for isochrone (travel-time catchment) calculations.', - title='Travel Time Unit', - ), - ] - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class Unit3(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[Unit3, Field(description='Distance unit.', title='Distance Unit')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.', - title='Transport Mode', - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class ApprovalStatus(StrEnum): - pending_review = 'pending_review' - approved = 'approved' - rejected = 'rejected' - - -class CreativeApproval(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Creative identifier')] - approval_status: Annotated[ - ApprovalStatus, - Field( - description='Approval state of a creative on a specific package', - title='Creative Approval Status', - ), - ] - rejection_reason: Annotated[ - str | None, - Field( - description="Human-readable explanation of why the creative was rejected. Present only when approval_status is 'rejected'." - ), - ] = None - - -class FormatIdsPendingItem(FormatId): - pass - - -class SnapshotUnavailableReason(StrEnum): - SNAPSHOT_UNSUPPORTED = 'SNAPSHOT_UNSUPPORTED' - SNAPSHOT_TEMPORARILY_UNAVAILABLE = 'SNAPSHOT_TEMPORARILY_UNAVAILABLE' - SNAPSHOT_PERMISSION_DENIED = 'SNAPSHOT_PERMISSION_DENIED' - - -class DeliveryStatus(StrEnum): - delivering = 'delivering' - not_delivering = 'not_delivering' - completed = 'completed' - budget_exhausted = 'budget_exhausted' - flight_ended = 'flight_ended' - goal_met = 'goal_met' - - -class Snapshot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - as_of: Annotated[ - AwareDatetime, - Field(description='ISO 8601 timestamp when this snapshot was captured by the platform'), - ] - staleness_seconds: Annotated[ - int, - Field( - description='Maximum age of this data in seconds. For example, 900 means the data may be up to 15 minutes old. Use this to interpret zero delivery: a value of 900 means zero impressions is likely real; a value of 14400 means reporting may still be catching up.', - ge=0, - ), - ] - impressions: Annotated[ - float, Field(description='Total impressions delivered since package start', ge=0.0) - ] - spend: Annotated[ - float, - Field( - description='Total spend since package start, denominated in snapshot.currency when present, otherwise package.currency or media_buy.currency', - ge=0.0, - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for spend in this snapshot. Optional when unchanged from package.currency or media_buy.currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - clicks: Annotated[ - float | None, Field(description='Total clicks since package start (when available)', ge=0.0) - ] = None - pacing_index: Annotated[ - float | None, - Field( - description='Current delivery pace relative to expected (1.0 = on track, <1.0 = behind, >1.0 = ahead). Absent when pacing cannot be determined.', - ge=0.0, - ), - ] = None - delivery_status: Annotated[ - DeliveryStatus | None, - Field( - description="Operational delivery state of this package. 'not_delivering' means the package is within its scheduled flight but has delivered zero impressions for at least one full staleness cycle — the signal for automated price adjustments or buyer alerts. Implementers must not return 'not_delivering' until at least staleness_seconds have elapsed since package activation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Optional extension object for seller-specific snapshot fields.', - title='Extension Object', - ), - ] = None - - -class Issue1(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue1] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - impairment = 'impairment' - creative_status_changed = 'creative.status_changed' - creative_purged = 'creative.purged' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class CanceledBy(StrEnum): - buyer = 'buyer' - seller = 'seller' - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Authentication1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class NotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication1 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: Annotated[ - Status1, - Field( - description='Account lifecycle status. See the Accounts Protocol overview for the operations matrix showing which tasks are permitted in each state.', - title='Account Status', - ), - ] - brand: Annotated[ - Brand | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: Annotated[ - Billing | None, - Field( - description="Who is invoiced on this account. See billing_entity for the invoiced party's business details.", - title='Billing Party', - ), - ] = None - billing_entity: Annotated[ - BillingEntity | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: Annotated[ - PaymentTerms | None, - Field( - description='Payment terms agreed for this account. Binding for all invoices when the account is active.', - title='Payment Terms', - ), - ] = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: Annotated[ - AccountScope | None, - Field( - description='How the seller scoped a billing account relative to the operator and brand dimensions.', - title='Account Scope', - ), - ] = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this media buy was canceled.') - ] - canceled_by: CanceledBy - reason: Annotated[ - str | None, Field(description='Reason the media buy was canceled.', max_length=500) - ] = None - - -class AvailableAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: MediaBuyValidAction - mode: Annotated[ - Mode, - Field( - description='The single mode that applies right now on this buy for this action. Singular because the buy has a concrete state, exactly one mode applies. Buyer SDKs branch on this to decide whether to expect a synchronous response, conditional handling, or an asynchronous approval callback.', - title='Media Buy Action Mode', - ), - ] - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this buy. Absence means no commitment, not zero commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class WebhookActivityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - idempotency_key: Annotated[ - str, - Field( - description='Equals the `idempotency_key` carried in the webhook payload itself (see docs/building/by-layer/L3/webhooks.mdx § Dedup by `idempotency_key`). Stable across retry attempts of the same logical fire — retries with `attempt` > 1 reuse this key. Buyers correlate this surface with their own endpoint logs via this exact field; the spec deliberately reuses the payload key rather than minting a parallel `delivery_id` so callers do not need a join table. Format is sender-defined; callers MUST treat as opaque.' - ), - ] - subscriber_id: Annotated[ - str | None, - Field( - description='Identifies which registered webhook subscriber received this fire. **Required on records from account-anchored notification channels** (`notification_configs[]` registered via `sync_accounts`) — every subscriber has a `subscriber_id` at registration time, the seller MUST echo it on every fire and every activity record. **Optional on records from per-resource push channels** (`push_notification_config` on a media buy or task) — the calling principal is unambiguous in single-subscriber configurations and the field MAY be omitted; sellers MUST populate it once `reporting_webhook` adopts multi-subscriber (per #3009 in AdCP 4.0). Buyers MUST NOT use absence as a signal that no other subscribers exist; that information is not exposed by this surface.' - ), - ] = None - fired_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when the seller initiated the HTTP request for this attempt.' - ), - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp when the seller observed the response (or terminal timeout / connection error — for `timeout` and `connection_error` outcomes, `completed_at` is set to the moment the seller declared the attempt terminal). Explicitly `null` when the attempt is still in flight or queued for retry (status `pending`); MUST be set as `null` rather than omitted so callers can distinguish 'still in flight' from 'field missing'." - ), - ] = None - notification_type: NotificationType - sequence_number: Annotated[ - int | None, - Field( - description='Sequence number from the webhook payload. Surfaced here so the buyer can spot stale-sequence drops and gaps without correlating against their own endpoint log. Absent for notification types that do not carry a sequence number.', - ge=0, - ), - ] = None - attempt: Annotated[ - int, - Field( - description='1-indexed retry counter for this logical fire. Initial fire is attempt=1; retries increment. Sellers MUST emit one record per attempt, so a successful first-attempt fire appears as a single record with `attempt: 1` and a 3-attempt retry trail appears as three records sharing `idempotency_key`.', - ge=1, - ), - ] - status: Annotated[ - Status3, - Field( - description="Outcome of this attempt. `success` — response received with 2xx (`http_status_code` populated). `failed` — response received with non-2xx (`http_status_code` populated). `timeout` — no response within the seller's configured timeout (`http_status_code` null). `connection_error` — DNS / TLS / socket failure before any HTTP response (`http_status_code` null). `pending` — attempt is in flight or queued for retry (`completed_at` null, `http_status_code` null). The `timeout` / `connection_error` split is intentional and operationally distinct: `timeout` typically signals a slow / overloaded buyer endpoint, `connection_error` typically signals it is unreachable or misconfigured." - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Target URL for this fire. Query string and fragment MUST be stripped before surfacing — buyers commonly stash bearer tokens in the query string and sellers MUST NOT echo those back through this debug surface. Sellers SHOULD additionally redact path segments matching obvious secret patterns (e.g., a path segment that is high-entropy random material or matches a UUID / token format). Buyers matching this against their own configured URL should compare by origin + path; query strings will not match and that mismatch is expected.' - ), - ] - http_status_code: Annotated[ - int | None, - Field( - description="HTTP status code returned by the buyer's endpoint. Explicitly `null` when no HTTP response was received (status `timeout`, `connection_error`, or `pending`); MUST be set as `null` rather than omitted.", - ge=100, - le=599, - ), - ] = None - response_time_ms: Annotated[ - int | None, - Field( - description='Wall-clock latency between request send and response receipt, in milliseconds. Explicitly `null` when the attempt did not complete (`timeout`, `connection_error`, `pending`); MUST be set as `null` rather than omitted.', - ge=0, - ), - ] = None - payload_size_bytes: Annotated[ - int | None, - Field( - description="Size of the request body the seller sent, in bytes. Useful for diagnosing oversized-payload rejections from the buyer's gateway.", - ge=0, - ), - ] = None - error_message: Annotated[ - str | None, - Field( - description='Short human-readable server-side classification of why this attempt did not succeed (e.g., `connection refused`, `TLS handshake timeout`, `HTTP 503 Service Unavailable`). Explicitly `null` for `success` (MUST be set as `null` rather than omitted). Sellers MUST NOT include request headers, request body content, or response body content in this field — payload surfacing is reserved for a future `include_webhook_payloads` extension and is subject to stricter access controls. Sellers SHOULD also avoid including buyer-endpoint internal hostnames, stack traces, or other implementation detail leaked by the response — keep it a stable classification string.', - max_length=500, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Resource-specific extension slot. Adopters MAY surface a resource-specific cross-reference (e.g., `creative_id` on a creative-lifecycle record, `media_buy_id` on a record nested inside an account-level read) under `ext` rather than adding top-level fields — the canonical record shape stays uniform across resources and the `ext` envelope absorbs per-resource needs. Top-level extensions are not permitted (`additionalProperties: false`).', - title='Extension Object', - ), - ] = None - - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas12(GeoPostalAreas1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas13(GeoPostalAreas2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas14(GeoPostalAreas3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas15(GeoPostalAreas4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas16(GeoPostalAreas5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas17(GeoPostalAreas6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas18(GeoPostalAreas7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas19(GeoPostalAreas8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas20(GeoPostalAreas9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas21(GeoPostalAreas10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21 - ] -): - root: Annotated[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude11(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude12(GeoPostalAreasExclude1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude13(GeoPostalAreasExclude2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude14(GeoPostalAreasExclude3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude15(GeoPostalAreasExclude4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude16(GeoPostalAreasExclude5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude17(GeoPostalAreasExclude6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude18(GeoPostalAreasExclude7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude19(GeoPostalAreasExclude8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude20(GeoPostalAreasExclude9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude21(GeoPostalAreasExclude10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21 - ] -): - root: Annotated[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude22(GeoPostalAreas22): - pass - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas22] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude22] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatformEnum] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class Cancellation1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') - ] - canceled_by: CanceledBy - reason: Annotated[ - str | None, Field(description='Reason the package was canceled.', max_length=500) - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's package identifier")] - product_id: Annotated[ - str | None, - Field( - description="Product identifier this package is purchased from. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Package budget amount, denominated in package.currency when present, otherwise media_buy.currency', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for monetary values at this package level (budget, bid_price, snapshot.spend). When absent, inherit media_buy.currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - bid_price: Annotated[ - float | None, - Field( - description='Current bid price for auction-based packages. Denominated in package.currency when present, otherwise media_buy.currency. Relevant for automated price optimization loops.', - ge=0.0, - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where another selector won precedence.', - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it.', - min_length=1, - ), - ] = None - format_kind: Annotated[ - FormatKind | None, - Field( - description='Direct canonical selector supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including informational-echo cases where another selector won precedence.', - title='Canonical Format Kind', - ), - ] = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`.' - ), - ] = None - impressions: Annotated[ - float | None, - Field(description='Goal impression count for impression-based packages', ge=0.0), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description='Targeting overlay applied to this package, echoed from the most recent create_media_buy or update_media_buy. Sellers SHOULD echo any persisted targeting so buyers can verify what was stored without replaying their own request. Sellers claiming the property-lists or collection-lists specialisms MUST include, within this targeting_overlay, the PropertyListReference / CollectionListReference they persisted.', - title='Targeting Overlay', - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight start time for this package. Use to determine whether the package is within its scheduled flight before interpreting delivery status.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 flight end time for this package') - ] = None - paused: Annotated[ - bool | None, Field(description='Whether this package is currently paused by the buyer') - ] = None - canceled: Annotated[ - bool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated.' - ), - ] = None - cancellation: Annotated[ - Cancellation1 | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged from the create_media_buy package request. Sellers MUST include persisted package context on read surfaces when the package was created through AdCP with context, so buyers can reconcile seller-assigned package_id values with their own line items; this is the legacy-safe fallback when an older seller did not echo product_id on the create response. Sellers MAY omit context for packages created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.', - title='Context Object', - ), - ] = None - creative_approvals: Annotated[ - list[CreativeApproval] | None, - Field( - description='Approval status for each creative assigned to this package. Absent when no creatives have been assigned.' - ), - ] = None - format_ids_pending: Annotated[ - list[FormatIdsPendingItem] | None, - Field( - description='Format IDs from the original create_media_buy format_ids_to_provide that have not yet been uploaded via sync_creatives. When empty or absent, all required formats have been provided.' - ), - ] = None - snapshot_unavailable_reason: Annotated[ - SnapshotUnavailableReason | None, - Field( - description='Machine-readable reason the snapshot is omitted. Present only when include_snapshot was true and snapshot is unavailable for this package.', - title='Snapshot Unavailable Reason', - ), - ] = None - snapshot: Annotated[ - Snapshot | None, - Field( - description='Near-real-time delivery snapshot for this package. Only present when include_snapshot was true in the request. Represents the latest available entity-level stats from the platform — not billing-grade data.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class MediaBuy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's unique identifier for the media buy")] - account: Annotated[ - Account | None, - Field( - description='Account billed for this media buy', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient | None, - Field( - description='Per-buy invoice recipient when provided at creation. Confirms the seller accepted the billing override. Bank details are omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - status: Annotated[ - Status2, Field(description='Status of a media buy.', title='Media Buy Status') - ] - status_as_of: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp indicating when the seller last refreshed the returned media-buy-level `status` from its source of truth. Use this to interpret cached or rolled-up list statuses, especially for curator/storefront aggregators where one buyer-facing buy maps to multiple upstream legs. For rolled-up statuses, this timestamp MUST NOT be later than the oldest upstream status observation that could affect the returned roll-up, so it never overstates freshness. Omit or return null to make no freshness assertion; buyers MUST NOT infer that an omitted or null value means the status is live. This is distinct from `updated_at`, which records when the media buy was last modified.' - ), - ] = None - health: Annotated[ - Health | None, - Field( - description='Dependency health of the media buy, orthogonal to `status`. `ok` (default) when no upstream resource that this buy depends on is in an offline state. `impaired` when at least one such resource (audience, creative, catalog_item, event_source, property) is offline and affects delivery for one or more packages — `impairments[]` MUST be non-empty in that case. On terminal-status buys, the seller MAY leave this field in whatever state held at the terminal transition. See lifecycle.mdx § Compliance and the impairment.coherence assertion.', - validate_default=True, - ), - ] = 'ok' # type: ignore[assignment] - impairments: Annotated[ - list[Impairment] | None, - Field( - description='Open impairments — upstream dependency state changes that affect delivery for at least one package on this buy. Empty when `health` is `ok`; non-empty iff `health` is `impaired` (health-iff rule on non-terminal buys). Sellers MUST add an entry on the next read after a referenced resource transitions to an offline state, and MUST remove the entry when the resource returns to a serviceable state or stops being a dependency (e.g., via assignment swap via update_media_buy). Staleness budget: the snapshot MUST reflect the impairment within 5 minutes of `impairment.observed_at` regardless of buyer poll cadence — sellers cannot rely on rare buyer polls to defer write propagation. See impairment.coherence assertion for the cross-resource invariant.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description="Reason provided by the seller when status is 'rejected'. Present only when status is 'rejected'." - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code (e.g., USD, EUR, GBP) for monetary values at this media buy level. total_budget is always denominated in this currency. Package-level fields may override with package.currency.', - pattern='^[A-Z]{3}$', - ), - ] - total_budget: Annotated[ - float, - Field( - description='Total budget amount across all packages, denominated in media_buy.currency', - ge=0.0, - ), - ] - start_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight start time for this media buy (earliest package start_time). Avoids requiring buyers to compute min(packages[].start_time).' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 flight end time for this media buy (latest package end_time). Avoids requiring buyers to compute max(packages[].end_time).' - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') - ] = None - confirmed_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller committed to this media buy. May be null until seller commitment occurs in deferred/manual approval flows. Once populated, remains stable through later pause, resume, activation, completion, cancellation, and reporting transitions.' - ), - ] - cancellation: Annotated[ - Cancellation | None, - Field(description="Cancellation metadata. Present only when status is 'canceled'."), - ] = None - revision: Annotated[ - int, - Field( - description='Current optimistic concurrency token. Pass this in update_media_buy requests intended to change state. Sellers increment it on mutating state changes/updates and reject stale tokens with CONFLICT when a revision token is provided.', - ge=1, - ), - ] - created_at: Annotated[AwareDatetime | None, Field(description='Creation timestamp')] = None - updated_at: Annotated[AwareDatetime | None, Field(description='Last update timestamp')] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque media-buy-level correlation data echoed unchanged from the create_media_buy request. Sellers MUST include persisted context on read surfaces when the media buy was created through AdCP with context, so buyers can reconcile seller-assigned media_buy_id values with their own tracking state. Sellers MAY omit context for media buys created outside AdCP or created without context. Sellers MUST NOT parse this object for business logic.', - title='Context Object', - ), - ] = None - valid_actions: Annotated[ - list[MediaBuyValidAction] | None, - Field( - description='Flat-vocabulary actions the buyer can perform on this media buy in its current state. Eliminates the need for agents to internalize the state machine — the seller declares what is permitted right now. Deprecated in favor of `available_actions[]`, which carries `mode` (self_serve / conditional_self_serve / requires_approval), optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.' - ), - ] = None - available_actions: Annotated[ - list[AvailableAction] | None, - Field( - description="Structured per-buy resolution of the actions buyer can perform right now. Authoritative — divergence from product `allowed_actions[]` is expected (negotiated terms, account tier, buy-level overrides live on the deal, not the product). Each entry carries the resolved `mode` (singular, since the buy has a concrete state), optional `sla` commitment, and optional `terms_ref`. Predicate queries via #4425's `requires` grammar address fields by dotted path, e.g. `available_actions.extend_flight.sla.response_max`. Absent SLA means no commitment, not zero commitment — callers composing duration predicates MUST also compose with `present: true` to avoid silently matching sellers who never declared one." - ), - ] = None - webhook_activity: Annotated[ - list[WebhookActivityItem] | None, - Field( - description="Recent reporting and health webhook fires for the calling principal, most-recent first. Present only when `include_webhook_activity` was true in the request AND the seller surfaces this debug capability for this buy. Three-state semantics: (a) field omitted — seller does not surface webhook activity (either does not persist fire history, or `capabilities.media_buy.propagation_surfaces` excludes webhook surfaces, or the buy has no registered `push_notification_config` for this principal); (b) empty array `[]` — seller persists fire history but has fired nothing recent for this principal; (c) non-empty array — actual fire records. Sellers whose declared `propagation_surfaces` does not include `webhook` MUST omit the field. **Retention (normative):** sellers that surface this field MUST retain records for at least 30 days from each record's `completed_at` (for records still in `pending` status the clock runs from `fired_at` until the attempt terminates, then resets to 30 days from `completed_at` — so retry trails do not age out mid-flight). Sellers that cannot honor the 30-day floor MUST omit the field entirely rather than return a shorter window. Sellers MAY return fewer than `webhook_activity_limit` records when fewer fire records exist within the retention window. Sellers MUST emit one record per attempt — single-attempt successes appear as a single record with `attempt: 1`. Record shape is canonical across resources: see [`/schemas/core/webhook-activity-record.json`](/schemas/v3/core/webhook-activity-record.json) and snapshot-and-log.mdx § Webhook activity log pattern.", - max_length=200, - ), - ] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Revision history entries, most recent first. Only present when include_history > 0 in the request. Each entry represents a state change or update to the media buy. Entries are append-only: sellers MUST NOT modify or delete previously emitted history entries. Callers MAY cache entries by revision number. Returns min(N, available entries) when include_history exceeds the total.' - ), - ] = None - packages: Annotated[ - list[Package], - Field( - description='Packages within this media buy, augmented with creative approval status and optional delivery snapshots' - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class GetMediaBuysResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - media_buys: Annotated[ - Sequence[MediaBuy], - Field( - description='Array of media buys with status, creative approval state, and optional delivery snapshots' - ), - ] - errors: Annotated[ - list[Error] | None, Field(description='Task-specific errors (e.g., media buy not found)') - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination metadata for the media_buys array.', title='Pagination Response' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py b/src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py deleted file mode 100644 index 1d440db0..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_products_request.py +++ /dev/null @@ -1,3425 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_products_request.json -# timestamp: 2026-06-18T11:30:22+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class BuyingMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - refine = 'refine' - - -class Refine1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field( - description='Change scoped to the overall request — direction for the selection as a whole.' - ), - ] = 'request' - ask: Annotated[ - str, - Field( - description="What the buyer is asking for at the request level (e.g., 'more video options and less display', 'suggest how to combine these products').", - min_length=1, - ), - ] - - -class Action(StrEnum): - include = 'include' - omit = 'omit' - more_like_this = 'more_like_this' - - -class Refine2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[Literal['product'], Field(description='Change scoped to a specific product.')] = 'product' - product_id: Annotated[ - str, Field(description='Product ID from a previous get_products response.', min_length=1) - ] - action: Annotated[ - Action | None, - Field( - description="'include' (default): return this product with updated pricing and data. 'omit': exclude this product from the response. 'more_like_this': find additional products similar to this one (the original is also returned). Optional — when omitted, the seller treats the entry as action: 'include'." - ), - ] = Action.include - ask: Annotated[ - str | None, - Field( - description="What the buyer is asking for on this product. For 'include': specific changes to request (e.g., 'add 16:9 format'). For 'more_like_this': what 'similar' means (e.g., 'same audience but video format'). Ignored when action is 'omit'.", - min_length=1, - ), - ] = None - - -class Action1(StrEnum): - include = 'include' - omit = 'omit' - finalize = 'finalize' - - -class Refine3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], Field(description='Change scoped to a specific proposal.') - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Proposal ID from a previous get_products response.', min_length=1) - ] - action: Annotated[ - Action1 | None, - Field( - description="'include' (default): return this proposal with updated allocations and pricing. 'omit': exclude this proposal from the response. 'finalize': request firm pricing and inventory hold — transitions a draft proposal to committed with an expires_at hold window. May trigger seller-side approval (HITL). The buyer should not set a time_budget for finalize requests — they represent a commitment to wait for the result. Optional — when omitted, the seller treats the entry as action: 'include'.\n\nFinalize is exclusive within the parent `refine[]` array: see the array-level description for the finalize-exclusivity rule (mixing finalize with non-finalize entries is rejected) and multi-finalize atomicity contract." - ), - ] = Action1.include - ask: Annotated[ - str | None, - Field( - description="What the buyer is asking for on this proposal (e.g., 'shift more budget toward video', 'reduce total by 10%'). Ignored when action is 'omit'.", - min_length=1, - ), - ] = None - - -class Refine(RootModel[Refine1 | Refine2 | Refine3]): - root: Annotated[Refine1 | Refine2 | Refine3, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Type(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class ConversionEvent(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIdType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: Annotated[ - Type, - Field( - description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema.", - title='Catalog Type', - ), - ] - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: Annotated[ - FeedFormat | None, - Field( - description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.', - title='Feed Format', - ), - ] = None - update_frequency: Annotated[ - UpdateFrequency | None, - Field( - description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.', - title='Update Frequency', - ), - ] = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[ConversionEvent] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: Annotated[ - ContentIdType | None, - Field( - description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum.", - title='Content ID Type', - ), - ] = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class Exclusivity(StrEnum): - none = 'none' - category = 'category' - exclusive = 'exclusive' - - -class PricingCurrency(RootModel[str]): - root: Annotated[ - str, - Field( - description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')", pattern='^[A-Z]{3}$' - ), - ] - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class BudgetRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[float | None, Field(description='Minimum budget amount', ge=0.0)] = None - max: Annotated[float | None, Field(description='Maximum budget amount', ge=0.0)] = None - currency: Annotated[ - str, - Field( - description="ISO 4217 currency code (e.g., 'USD', 'EUR', 'GBP')", pattern='^[A-Z]{3}$' - ), - ] - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class Region(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]+$')] - - -class Channel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class VideoPlacementType(StrEnum): - instream = 'instream' - accompanying_content = 'accompanying_content' - interstitial = 'interstitial' - standalone = 'standalone' - - -class AudioDistributionType(StrEnum): - music_streaming_service = 'music_streaming_service' - fm_am_broadcast = 'fm_am_broadcast' - podcast = 'podcast' - catch_up_radio = 'catch_up_radio' - web_radio = 'web_radio' - video_game = 'video_game' - text_to_speech = 'text_to_speech' - - -class SponsoredPlacementType(StrEnum): - sponsored_search = 'sponsored_search' - sponsored_display = 'sponsored_display' - sponsored_native = 'sponsored_native' - - -class SocialPlacementSurface(StrEnum): - feed = 'feed' - stories = 'stories' - short_video = 'short_video' - explore = 'explore' - search = 'search' - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[AnyUrl, Field(description="Provider's agent URL from the registry.")] - context_match: Annotated[ - bool | None, Field(description='When true, require this provider to support context match.') - ] = None - identity_match: Annotated[ - bool | None, - Field(description='When true, require this provider to support identity match.'), - ] = None - - -class ResponseType(StrEnum): - activation = 'activation' - catalog_items = 'catalog_items' - creative = 'creative' - deal = 'deal' - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - providers: Annotated[ - list[Provider] | None, - Field( - description='Filter to products with specific TMP providers and match types. Each entry identifies a provider by agent_url and optionally requires specific match capabilities. Products must match at least one entry.', - min_length=1, - ), - ] = None - response_types: Annotated[ - list[ResponseType] | None, - Field( - description="Filter to products supporting specific TMP response types (e.g., 'activation', 'creative', 'catalog_items'). Products must support at least one of the listed types.", - min_length=1, - ), - ] = None - - -class RequiredFeatures(AdCPBaseModel): - inline_creative_management: Annotated[ - bool | None, - Field( - description='Supports creatives provided inline in create_media_buy and update_media_buy package payloads. This flag does not imply a creative library: an inline-only seller can accept packages[].creatives without advertising sync_creatives, list_creatives, or reusable creative IDs.' - ), - ] = None - property_list_filtering: Annotated[ - bool | None, - Field( - description='Honors property_list parameter in get_products to filter results to buyer-approved properties' - ), - ] = None - catalog_management: Annotated[ - bool | None, - Field( - description='Supports sync_catalogs task for catalog feed management with platform review and approval' - ), - ] = None - committed_metrics_supported: Annotated[ - bool | None, - Field( - description="Seller has per-package snapshot infrastructure for the reporting contract. When true, the seller MUST populate `package.committed_metrics` on committed `create_media_buy` responses where `confirmed_at` is non-null, MUST omit `package.committed_metrics` while `confirmed_at` is null for a provisional buy, and MUST honor append-only mid-flight metric additions via `update_media_buy`. The unified `committed_metrics` array (per the metric-accountability design) covers both standard and vendor-defined metric entries, so a single flag is load-bearing. Buyers filtering on this flag are detecting 'this seller can stamp the reporting contract,' which closes the audit gap from PR #3510 where absence of `committed_metrics` was indistinguishable between 'didn't snapshot' and 'snapshot infrastructure not implemented.'" - ), - ] = None - - -class Level(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class RequiredGeoTargetingItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - level: Annotated[ - Level, - Field( - description='Geographic targeting level (country, region, metro, postal_area)', - title='Geographic Targeting Level', - ), - ] - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area system filters; not applicable to country, region, or metro filters.', - pattern='^[A-Z]{2}$', - ), - ] = None - system: Annotated[ - str | None, - Field( - description="Optional classification system within the level. Use for a specific metro system (e.g., 'nielsen_dma'), native postal_area system (e.g., 'zip' with country 'US'), or deprecated legacy postal alias (e.g., 'us_zip'). Not applicable for country/region which use ISO standards." - ), - ] = None - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalTargetingItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargetingItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargetingItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class TargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class SignalTargetingItem4(AdCPBaseModel): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem5(SignalTargetingItem1, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem6(SignalTargetingItem2, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem7(SignalTargetingItem3, SignalTargetingItem4): - targeting_mode: Annotated[ - TargetingMode | None, - Field( - description="Desired use of this signal on the product. 'include' requires the product option to allow use in an any group. 'exclude' requires the product option to allow use in a none group." - ), - ] = TargetingMode.include - - -class SignalTargetingItem( - RootModel[SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7] -): - root: SignalTargetingItem5 | SignalTargetingItem6 | SignalTargetingItem7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class PostalAreas1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class PostalAreas2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class PostalAreas3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country1(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class PostalAreas4(AdCPBaseModel): - country: Annotated[Country1, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class PostalAreas5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class PostalAreas6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class PostalAreas7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class PostalAreas8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class PostalAreas9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class PostalAreas10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Unit(StrEnum): - min = 'min' - hr = 'hr' - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit', ge=1.0)] - unit: Annotated[ - Unit, - Field( - description='Time unit for isochrone (travel-time catchment) calculations.', - title='Travel Time Unit', - ), - ] - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class Unit1(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance', gt=0.0)] - unit: Annotated[Unit1, Field(description='Distance unit', title='Distance Unit')] - - -class Type1(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type1, Field(description='GeoJSON geometry type')] - coordinates: Annotated[list[Any], Field(description='GeoJSON coordinates array')] - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, Field(description='Latitude in decimal degrees (WGS 84)', ge=-90.0, le=90.0) - ] = None - lng: Annotated[ - float | None, - Field(description='Longitude in decimal degrees (WGS 84)', ge=-180.0, le=180.0), - ] = None - label: Annotated[ - str | None, - Field(description="Human-readable label (e.g., 'Düsseldorf', 'Heathrow Airport')"), - ] = None - travel_time: Annotated[ - TravelTime | None, Field(description='Travel time limit for isochrone calculation') - ] = None - transport_mode: Annotated[ - TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.', - title='Transport Mode', - ), - ] = None - radius: Annotated[Radius | None, Field(description='Simple radius from the point')] = None - geometry: Annotated[ - Geometry | None, - Field(description='Pre-computed GeoJSON geometry defining the proximity boundary'), - ] = None - - -class Metric(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class Standard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class RequiredMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class Keyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: Annotated[ - MatchType | None, - Field( - description='Keyword targeting match type. broad: ads may serve on queries semantically related to the keyword. phrase: ads serve when the query contains the keyword phrase. exact: ads serve only when the query matches the keyword exactly.', - title='Match Type', - ), - ] = MatchType.broad - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class FieldModel(StrEnum): - product_id = 'product_id' - name = 'name' - description = 'description' - publisher_properties = 'publisher_properties' - channels = 'channels' - video_placement_types = 'video_placement_types' - audio_distribution_types = 'audio_distribution_types' - sponsored_placement_types = 'sponsored_placement_types' - social_placement_surfaces = 'social_placement_surfaces' - format_ids = 'format_ids' - format_options = 'format_options' - placements = 'placements' - delivery_type = 'delivery_type' - exclusivity = 'exclusivity' - pricing_options = 'pricing_options' - forecast = 'forecast' - outcome_measurement = 'outcome_measurement' - delivery_measurement = 'delivery_measurement' - reporting_capabilities = 'reporting_capabilities' - creative_policy = 'creative_policy' - catalog_types = 'catalog_types' - metric_optimization = 'metric_optimization' - conversion_tracking = 'conversion_tracking' - data_provider_signals = 'data_provider_signals' - included_signals = 'included_signals' - signal_targeting_allowed = 'signal_targeting_allowed' - signal_targeting_options = 'signal_targeting_options' - signal_targeting_rules = 'signal_targeting_rules' - max_optimization_goals = 'max_optimization_goals' - catalog_match = 'catalog_match' - collections = 'collections' - collection_targeting_allowed = 'collection_targeting_allowed' - installments = 'installments' - brief_relevance = 'brief_relevance' - expires_at = 'expires_at' - product_card = 'product_card' - product_card_detailed = 'product_card_detailed' - enforced_policies = 'enforced_policies' - trusted_match = 'trusted_match' - - -class Unit2(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class TimeBudget(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit2, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class DeliveryType(StrEnum): - guaranteed = 'guaranteed' - non_guaranteed = 'non_guaranteed' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand1, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Metro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - code: Annotated[ - str, Field(description="Metro code within the system (e.g., '501' for NYC DMA)") - ] - - -class PostalAreas11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas12(PostalAreas1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas13(PostalAreas2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas14(PostalAreas3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas15(PostalAreas4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas16(PostalAreas5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas17(PostalAreas6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas18(PostalAreas7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas19(PostalAreas8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas20(PostalAreas9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas21(PostalAreas10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class PostalAreas( - RootModel[ - PostalAreas12 - | PostalAreas13 - | PostalAreas14 - | PostalAreas15 - | PostalAreas16 - | PostalAreas17 - | PostalAreas18 - | PostalAreas19 - | PostalAreas20 - | PostalAreas21 - ] -): - root: Annotated[ - PostalAreas12 - | PostalAreas13 - | PostalAreas14 - | PostalAreas15 - | PostalAreas16 - | PostalAreas17 - | PostalAreas18 - | PostalAreas19 - | PostalAreas20 - | PostalAreas21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class PostalAreas22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class RequiredPerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: Annotated[ - Standard | None, - Field( - description="Measurement standard. Required when metric is 'viewability' (MRC and GroupM define materially different thresholds). Omit for other metrics.", - title='Viewability Standard', - ), - ] = None - vendor: Annotated[ - Vendor, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class RequiredVendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor1 | None, - Field( - description='Pin to a specific vendor. Optional — omit to match any vendor offering this `metric_id`.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - metric_id: Annotated[ - str | None, - Field( - description="Pin to an exact metric identifier within a vendor's vocabulary. Optional — omit to match any metric from the pinned vendor. Cross-vendor identifier matching only works after vendors converge on identifiers (typically post-standardization).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] = None - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - delivery_type: DeliveryType | None = None - exclusivity: Annotated[ - Exclusivity | None, - Field( - description="Filter by exclusivity level. Returns products matching the specified exclusivity (e.g., 'exclusive' returns only sole-sponsorship products).", - title='Exclusivity', - ), - ] = None - is_fixed_price: Annotated[ - bool | None, - Field( - description='Filter by pricing availability and returned pricing options: true = products offering fixed pricing (at least one option with fixed_price), false = products offering auction pricing (at least one option without fixed_price). Products with both fixed and auction options match both true and false, but sellers MUST return only the pricing_options entries matching the requested pricing type so buyers can deterministically select from the returned options.' - ), - ] = None - pricing_currencies: Annotated[ - list[PricingCurrency] | None, - Field( - description='Filter by currencies the buyer can use for the media product transaction, using ISO 4217 currency codes. Products match when they offer at least one product-level pricing_options entry in one of the requested currencies and any seller-applied or otherwise mandatory product-scoped signal charges are satisfiable in one of those currencies or have no incremental price. Mandatory custom signal pricing without currency is not satisfiable for this filter unless the seller can truthfully treat it as having no incremental price. Sellers MUST return only product pricing_options entries whose currency is in this list so buyers can select deterministically from discovery. This filter does not require pruning optional signal or vendor add-on pricing; buyers should avoid optional add-ons priced only in unsupported currencies.', - min_length=1, - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, Field(description='Filter by specific format IDs', min_length=1) - ] = None - standard_formats_only: Annotated[ - bool | None, Field(description='Only return products accepting IAB standard formats') - ] = None - min_exposures: Annotated[ - int | None, - Field(description='Minimum exposures/impressions needed for measurement validity', ge=1), - ] = None - start_date: Annotated[ - date_aliased | None, - Field( - description='Campaign start date (ISO 8601 date format: YYYY-MM-DD) for availability checks' - ), - ] = None - end_date: Annotated[ - date_aliased | None, - Field( - description='Campaign end date (ISO 8601 date format: YYYY-MM-DD) for availability checks' - ), - ] = None - budget_range: Annotated[ - BudgetRange | None, Field(description='Budget range to filter appropriate products') - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description="Filter by country coverage using ISO 3166-1 alpha-2 codes (e.g., ['US', 'CA', 'GB']). Works for all inventory types.", - min_length=1, - ), - ] = None - regions: Annotated[ - list[Region] | None, - Field( - description="Filter by region coverage using ISO 3166-2 codes (e.g., ['US-NY', 'US-CA', 'GB-SCT']). Use for locally-bound inventory (regional OOH, local TV) where products have region-specific coverage.", - min_length=1, - ), - ] = None - metros: Annotated[ - list[Metro] | None, - Field( - description='Filter by metro coverage for locally-bound inventory (radio, DOOH, local TV). Use when products have DMA/metro-specific coverage. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.', - min_length=1, - ), - ] = None - channels: Annotated[ - list[Channel] | None, - Field( - description="Filter by advertising channels (e.g., ['display', 'ctv', 'dooh'])", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Filter video products by acceptable declared video placement types, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested video placement types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Filter audio products by acceptable declared audio distribution types, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested audio distribution types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Filter retail-media products by acceptable declared sponsored-placement types (sponsored search, sponsored display, or sponsored native). Sellers SHOULD return only products they can satisfy with at least one requested type. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested sponsored-placement types SHOULD NOT match unless the seller can constrain delivery to the requested type during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Filter social products by acceptable declared social-placement surfaces (feed, stories, short_video, explore, or search). Sellers SHOULD return only products they can satisfy with at least one requested surface. Products whose only available delivery is a mixed, non-targetable bundle that includes unrequested surfaces SHOULD NOT match unless the seller can constrain delivery to the requested surface during planning or purchase. This filter has set semantics for wholesale feed canonicalization.', - min_length=1, - ), - ] = None - required_axe_integrations: Annotated[ - list[AnyUrl] | None, - Field( - deprecated=True, - description='Deprecated: Use trusted_match filter instead. Filter to products executable through specific agentic ad exchanges. URLs are canonical identifiers.', - ), - ] = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Filter products by Trusted Match Protocol capabilities. Only products with matching TMP support are returned.' - ), - ] = None - required_features: Annotated[ - RequiredFeatures | None, - Field( - description='Filter to products from sellers supporting specific protocol features. Only features set to true are used for filtering.', - title='Media Buy Features', - ), - ] = None - required_geo_targeting: Annotated[ - list[RequiredGeoTargetingItem] | None, - Field( - description='Filter to products from sellers supporting specific geo targeting capabilities. Each entry specifies a targeting level (country, region, metro, postal_area) and optionally a system for levels that have multiple classification systems. For native postal_area filters, include country plus the country-local postal system.', - min_length=1, - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargetingItem] | None, - Field( - description="Filter to products where the requested signals are buyer-selectable and jointly composable: the signals are available through inline signal_targeting_options and/or through get_signals for wholesale products that allow signal targeting but omit inline options, signal_targeting_allowed is true, and the requested set can coexist under the product's signal_targeting_rules. Each filter entry uses signal_ref, with deprecated signal_id accepted during the SignalRef migration window, and may include targeting_mode='include' or 'exclude' to require the product option or product rules to support that use. When targeting_mode is omitted, include is assumed. SignalRef scope 'product' is seller-local exact option matching only, not a portable semantic identifier across products or sellers; buyers wanting portable discovery should use scope 'data_provider' or get_signals. included_signals and deprecated bundled/non-selectable data_provider_signals do not satisfy this filter because they cannot be selected on create_media_buy.", - min_length=1, - ), - ] = None - postal_areas: Annotated[ - list[PostalAreas | PostalAreas22] | None, - Field( - description='Filter by postal area coverage for locally-bound inventory (direct mail, DOOH, local campaigns). Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility. For digital inventory where products have broad coverage, use required_geo_targeting instead to filter by seller capability.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Filter by proximity to geographic points. Returns products with inventory coverage near these locations. Follows the same format as the targeting overlay — each entry uses exactly one method: travel_time + transport_mode, radius, or geometry. For locally-bound inventory (DOOH, radio), filters to products with coverage in the area. For digital inventory, filters to products from sellers supporting geo_proximity targeting.', - min_length=1, - ), - ] = None - required_performance_standards: Annotated[ - list[RequiredPerformanceStandard] | None, - Field( - description="Filter to products that can meet the buyer's performance standard requirements. Each entry specifies a metric, minimum threshold, and optionally a required vendor and standard. Products that cannot meet these thresholds or do not support the specified vendors are excluded. Use this to tell the seller upfront: 'I need DoubleVerify for viewability at 70% MRC.'", - min_length=1, - ), - ] = None - required_metrics: Annotated[ - list[RequiredMetric] | None, - Field( - description="Filter to products whose `reporting_capabilities.available_metrics` is a superset of these metrics — i.e., products that commit to reporting all listed metrics in delivery responses. Use this for capability-level discovery (e.g., 'I need products that report `completed_views` for a CTV CPCV buy'); guarantee-level requirements with thresholds belong in `required_performance_standards` and `measurement_terms`. Sellers MUST silently exclude products that cannot meet this list (filter-not-fail; do not return an error). The product's declared `available_metrics` becomes the binding reporting contract carried into the resulting media buy — the same metric vocabulary is used to compute `missing_metrics` on `get_media_buy_delivery`.", - examples=[ - ['completed_views'], - ['completed_views', 'completion_rate'], - ['impressions', 'spend', 'engagements'], - ], - min_length=1, - ), - ] = None - required_vendor_metrics: Annotated[ - list[RequiredVendorMetric] | None, - Field( - description="Filter to products whose `reporting_capabilities.vendor_metrics` matches these criteria. Each entry pins a `vendor` (matches any metric from that vendor), a `metric_id` (matches the metric across any vendor that uses that identifier), or both (specific vendor's specific metric). A product matches if its declared `vendor_metrics` covers ALL listed entries (AND across entries; pins within an entry are conjunctive). Cross-vendor discovery (e.g., 'I need attention measurement from any vendor that does it') is the buyer agent's responsibility — the agent resolves which vendors offer a category via the vendors' `brand.json` records, then enumerates them as filter entries. AdCP does not carry vendor-side metric metadata (category, methodology, standard alignment) in the filter surface; that lives at the vendor and is queried out-of-band. Sellers MUST silently exclude non-matching products (filter-not-fail; do not return an error) — same convention as the other `required_*` filters.", - examples=[ - [{'vendor': {'domain': 'attentionvendor.example'}}], - [ - { - 'vendor': {'domain': 'panelmeasurement.example'}, - 'metric_id': 'demographic_reach', - } - ], - [ - {'vendor': {'domain': 'attentionvendor.example'}}, - {'vendor': {'domain': 'secondattentionvendor.example'}}, - ], - ], - min_length=1, - ), - ] = None - keywords: Annotated[ - list[Keyword] | None, - Field( - description='Filter by keyword relevance for search and retail media platforms. Returns products that support keyword targeting for these terms. Allows the sell-side agent to assess keyword availability and recommend appropriate products. Use match_type to indicate the desired precision.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Vendor-namespaced extension parameters for seller-specific filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; do not interpolate into LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.', - title='Extension Object', - ), - ] = None - - -class GetProductsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - buying_mode: Annotated[ - BuyingMode, - Field( - description="Declares buyer intent for this request. 'brief': publisher curates product recommendations from the provided brief. 'wholesale': buyer requests raw product inventory to apply their own audiences — brief must not be provided, and proposals are omitted. 'refine': iterate on products and proposals from a previous get_products response using the refine array of change requests. v3 clients MUST include buying_mode. Sellers receiving requests from pre-v3 clients without buying_mode SHOULD default to 'brief'. Timing semantics: 'wholesale' is a wholesale product feed read — sellers SHOULD return a synchronous response and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field (with optional estimated_wait), not via a task-handoff envelope. 'brief' and 'refine' MAY complete synchronously, or MAY return a Submitted envelope (see get-products-async-response-submitted.json) when curation requires upstream-system queries or HITL review the seller cannot complete inside time_budget. Buyers needing predictable fast wholesale product feed access MUST use 'wholesale'; buyers open to slower curation use 'brief' or 'refine'." - ), - ] - brief: Annotated[ - str | None, - Field( - description="Natural language description of campaign requirements. Required when buying_mode is 'brief'. Must not be provided when buying_mode is 'wholesale' or 'refine'." - ), - ] = None - refine: Annotated[ - list[Refine] | None, - Field( - description="Array of change requests for iterating on products and proposals from a previous get_products response. Each entry declares a scope (request, product, or proposal) and what the buyer is asking for. Only valid when buying_mode is 'refine'. The seller responds to each entry via refinement_applied in the response, matched by position.\n\nFinalize-exclusivity rule: if any entry has `action: 'finalize'`, ALL entries in the array MUST be proposal-scoped with `action: 'finalize'` — mixing finalize entries with `include`/`omit` entries or with request- / product-scoped entries MUST be rejected by the seller with `INVALID_REQUEST`. Finalize is a commit, not a refinement; the buyer expressing intent to commit means refinements have already converged. Buyers needing to refine AND commit in close succession sequence the calls: first a refine call (no finalize), then a finalize call against the resulting `proposal_id`(s).\n\nMulti-finalize semantics: multiple finalize entries against different `proposal_id` values in a single call are allowed and MUST be **atomic at the observation point** — sellers MUST NOT return a success response unless every named proposal has both completed and been persisted as committed. Pre-commit validation runs before any side-effects (inventory pull, terms lock, governance attestation); if any proposal fails validation, the seller MUST reject the entire call without committing any of the named proposals. There is no rollback operation in the spec — an `unfinalize` would itself be a new mutation surface; the atomicity guarantee runs entirely on the seller's pre-commit validation gate, not on post-commit reversal. Sellers that cannot guarantee atomic pre-commit validation MUST reject multi-finalize arrays with `MULTI_FINALIZE_UNSUPPORTED` (preferred — distinguishes seller-side capability gap from a malformed request) or `INVALID_REQUEST` (acceptable fallback for sellers on a pre-3.1 error catalog). If a mid-commit failure occurs *after* validation passed but before all proposals persist (e.g., a downstream ad server fails between commits one and two), the seller MUST return `INTERNAL_ERROR` with `refinement_applied[]` carrying per-position outcomes — the spec does NOT define a recovery path for this case, and buyers SHOULD treat the resulting state as undefined and re-read via `get_media_buys` / equivalent before retrying. Buyers MUST NOT assume multi-finalize support without a successful first attempt — there is no capability flag for this; the failure response is the discovery surface. Buyers whose intent specifically requires atomic commit (e.g., budget-shared proposals where one finalizing without the other is incoherent) MUST be prepared to abandon the intent if the seller returns `MULTI_FINALIZE_UNSUPPORTED` — there is no recovery for that loss of buyer intent beyond sequencing single-finalize calls and accepting the looser commit guarantee.", - min_length=1, - ), - ] = None - brand: Annotated[ - Brand | None, - Field( - description='Brand reference for product discovery context. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - catalog: Annotated[ - Catalog | None, - Field( - description='Catalog of items the buyer wants to promote. The seller matches catalog items against its inventory and returns products where matches exist. Supports all catalog types: a job catalog finds job ad products, a product catalog finds sponsored product slots. Reference a synced catalog by catalog_id, or provide inline items.', - examples=[ - { - 'description': 'Synced product catalog from Google Merchant Center', - 'data': { - 'catalog_id': 'gmc-primary', - 'name': 'Primary Product Feed', - 'type': 'product', - 'url': 'https://feeds.acmecorp.com/products.xml', - 'feed_format': 'google_merchant_center', - 'update_frequency': 'daily', - }, - }, - { - 'description': 'Inventory feed for store-level stock data', - 'data': { - 'catalog_id': 'store-inventory', - 'name': 'Store Inventory', - 'type': 'inventory', - 'url': 'https://feeds.acmecorp.com/inventory.json', - 'feed_format': 'custom', - 'update_frequency': 'hourly', - }, - }, - { - 'description': 'Store locator feed', - 'data': { - 'catalog_id': 'retail-locations', - 'name': 'Retail Locations', - 'type': 'store', - 'url': 'https://feeds.acmecorp.com/stores.json', - 'feed_format': 'custom', - 'update_frequency': 'weekly', - }, - }, - { - 'description': 'Promotional pricing feed', - 'data': { - 'catalog_id': 'summer-sale', - 'name': 'Summer Sale Promotions', - 'type': 'promotion', - 'url': 'https://feeds.acmecorp.com/promotions.json', - 'feed_format': 'google_merchant_center', - 'update_frequency': 'daily', - }, - }, - { - 'description': 'Inline offering catalog (no sync needed)', - 'data': { - 'type': 'offering', - 'items': [ - { - 'offering_id': 'summer-sale', - 'name': 'Summer Sale', - 'landing_url': 'https://acme.com/summer', - } - ], - }, - }, - { - 'description': 'Reference to a previously synced catalog by ID', - 'data': { - 'catalog_id': 'gmc-primary', - 'type': 'product', - 'ids': ['SKU-12345', 'SKU-67890'], - }, - }, - { - 'description': 'Product catalog with GTIN cross-retailer matching and attribution', - 'data': { - 'type': 'product', - 'gtins': ['00013000006040', '00013000006057'], - 'content_id_type': 'gtin', - 'conversion_events': ['purchase', 'add_to_cart'], - }, - }, - { - 'description': 'Inline store catalog with catchment areas', - 'data': { - 'catalog_id': 'retail-locations', - 'name': 'Retail Locations', - 'type': 'store', - 'items': [ - { - 'store_id': 'amsterdam-flagship', - 'name': 'Amsterdam Flagship', - 'location': {'lat': 52.3676, 'lng': 4.9041}, - 'catchments': [ - { - 'catchment_id': 'walk', - 'travel_time': {'value': 10, 'unit': 'min'}, - 'transport_mode': 'walking', - }, - { - 'catchment_id': 'drive', - 'travel_time': {'value': 15, 'unit': 'min'}, - 'transport_mode': 'driving', - }, - ], - } - ], - }, - }, - ], - title='Catalog', - ), - ] = None - account: Annotated[ - Account | Account1 | None, - Field( - description="Account for product lookup. Returns products with pricing specific to this account's rate card.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - preferred_delivery_types: Annotated[ - list[DeliveryType] | None, - Field( - description='Delivery types the buyer prefers, in priority order. Unlike filters.delivery_type which excludes non-matching products, this signals preference for curation — the publisher may still include other delivery types when they match the brief well.', - min_length=1, - ), - ] = None - filters: Annotated[ - Filters | None, - Field(description='Structured filters for product discovery', title='Product Filters'), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description='[AdCP 3.0] Reference to an externally managed property list. When provided, the sales agent should filter products to only those available on properties in the list.', - title='Property List Reference', - ), - ] = None - fields: Annotated[ - list[FieldModel] | None, - Field( - description='Specific product fields to include in the response. When omitted, all fields are returned. Use for lightweight discovery calls where only a subset of product data is needed (e.g., just IDs and pricing for comparison). Required fields (product_id, name) are always included regardless of selection.', - min_length=1, - ), - ] = None - time_budget: Annotated[ - TimeBudget | None, - Field( - description='Maximum time the buyer will commit to this request. The seller returns the best results achievable within this budget and does not start processes (human approvals, expensive external queries) that cannot complete in time. When omitted, the seller decides timing.' - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on curated discovery. Meaningful only for `buying_mode: "brief"` and `buying_mode: "refine"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief/refine request includes this field and the seller returns a Submitted envelope, the seller MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the seller cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: sellers MUST NOT route `buying_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.', - title='Push Notification Config', - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - if_wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_products response from this agent. Only valid when buying_mode is wholesale. When provided, the seller compares against its current wholesale product feed version for the buyer's cache_scope and MAY return an unchanged: true response (with products omitted) if nothing has changed. The token is scope-keyed: buyers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. Backward-compatible: pre-v3.1 agents that ignore this field simply return the full payload, same as the unchanged-server path. See specs/wholesale-feed-webhooks.md for the full sync pattern." - ), - ] = None - if_pricing_version: Annotated[ - str | None, - Field( - description="Opaque pricing_version token from a prior get_products response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → seller returns the full payload (pricing is implicitly stale); (2) if_wholesale_feed_version matches but if_pricing_version mismatches → seller returns the full payload so the buyer sees updated pricing_options; (3) both match → seller MAY return unchanged: true. Agents that don't track pricing separately ignore if_pricing_version and fall back to if_wholesale_feed_version semantics. Useful for storefronts that re-price compositions far more often than they re-render product mirrors." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - required_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs that the buyer requires to be enforced for products in this response. Sellers filter products to only those that comply with or already enforce the requested policies.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/get_products_response.py b/src/adcp/types/generated_poc/bundled/media_buy/get_products_response.py deleted file mode 100644 index f73dc0d1..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/get_products_response.py +++ /dev/null @@ -1,26473 +0,0 @@ -# generated by datamodel-codegen: -# filename: get_products_response.json -# timestamp: 2026-06-18T11:30:47+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class PublisherDomain(RootModel[str]): - root: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class PublisherProperty1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class PublisherProperty2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PropertyTag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class PublisherProperty3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -class PublisherProperty4(AdCPBaseModel): - pass - - -class PublisherProperty5(PublisherProperty1, PublisherProperty4): - pass - - -class PublisherProperty6(PublisherProperty2, PublisherProperty4): - pass - - -class PublisherProperty7(PublisherProperty3, PublisherProperty4): - pass - - -class PublisherProperty(RootModel[PublisherProperty5 | PublisherProperty6 | PublisherProperty7]): - root: PublisherProperty5 | PublisherProperty6 | PublisherProperty7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class V1FormatRefItem(FormatId): - pass - - -class FormatSchema(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class PlatformExtension(FormatSchema): - pass - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(RootModel[str]): - root: Annotated[ - str, - Field( - examples=[ - 'list_creatives', - 'sync_creatives', - 'create_media_buy', - 'get_media_buy_delivery', - 'get_creative_delivery', - ], - min_length=1, - ), - ] - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status1(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class RequiredConnection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status1 | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[int, Field(ge=1)] - height: Annotated[int, Field(ge=1)] - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RequiredConnection1(RequiredConnection): - pass - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class RequiredConnection2(RequiredConnection): - pass - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class RequiredConnection3(RequiredConnection): - pass - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class RequiredConnection4(RequiredConnection): - pass - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(RootModel[int]): - root: Annotated[int, Field(ge=0)] - - -class VideoCodec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class AudioCodec(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(RootModel[int]): - root: Annotated[int, Field(ge=1)] - - -class CompanionBannerHeight(CompanionBannerWidth): - pass - - -class RequiredConnection5(RequiredConnection): - pass - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class RequiredConnection6(RequiredConnection): - pass - - -class AudioCodec1(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class AudioSampleRate(CompanionBannerWidth): - pass - - -class AudioChannel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class RequiredConnection7(RequiredConnection): - pass - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class RequiredConnection8(RequiredConnection): - pass - - -class SupportedCatalogType(StrEnum): - product = 'product' - store = 'store' - offering = 'offering' - hotel = 'hotel' - flight = 'flight' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - job = 'job' - inventory = 'inventory' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class RequiredConnection9(RequiredConnection): - pass - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat1(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource3(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class RequiredConnection10(RequiredConnection): - pass - - -class RequiredConnection11(RequiredConnection): - pass - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class Kind(StrEnum): - publisher_ref = 'publisher_ref' - seller_inline = 'seller_inline' - - -class Mode(StrEnum): - targetable = 'targetable' - included = 'included' - - -class RequiredConnection12(RequiredConnection): - pass - - -class RequiredConnection13(RequiredConnection): - pass - - -class RequiredConnection14(RequiredConnection): - pass - - -class RequiredConnection15(RequiredConnection): - pass - - -class RequiredConnection16(RequiredConnection): - pass - - -class RequiredConnection17(RequiredConnection): - pass - - -class RequiredConnection18(RequiredConnection): - pass - - -class RequiredConnection19(RequiredConnection): - pass - - -class RequiredConnection20(RequiredConnection): - pass - - -class RequiredConnection21(RequiredConnection): - pass - - -class RequiredConnection22(RequiredConnection): - pass - - -class RequiredConnection23(RequiredConnection): - pass - - -class DeliveryType(StrEnum): - guaranteed = 'guaranteed' - non_guaranteed = 'non_guaranteed' - - -class Exclusivity(StrEnum): - none = 'none' - category = 'category' - exclusive = 'exclusive' - - -class PriceGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - p25: Annotated[ - float | None, Field(description='25th percentile of recent winning bids', ge=0.0) - ] = None - p50: Annotated[float | None, Field(description='Median of recent winning bids', ge=0.0)] = None - p75: Annotated[ - float | None, Field(description='75th percentile of recent winning bids', ge=0.0) - ] = None - p90: Annotated[ - float | None, Field(description='90th percentile of recent winning bids', ge=0.0) - ] = None - - -class ViewThreshold(RootModel[float]): - root: Annotated[ - float, - Field( - description='Percentage completion threshold (0.0 to 1.0, e.g., 0.5 = 50%)', - ge=0.0, - le=1.0, - ), - ] - - -class ViewThreshold1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - duration_seconds: Annotated[int, Field(description='Seconds of viewing required', ge=1)] - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold | ViewThreshold1 - - -class Parameters2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['dooh'], Field(description='Discriminator identifying this as DOOH parameters') - ] = 'dooh' - sov_percentage: Annotated[ - float | None, - Field(description='Guaranteed share of voice as a percentage (0-100)', ge=0.0, le=100.0), - ] = None - loop_duration_seconds: Annotated[ - int | None, Field(description='Duration of the ad loop rotation in seconds', ge=1) - ] = None - min_plays_per_hour: Annotated[ - int | None, Field(description='Minimum number of plays per hour guaranteed', ge=1) - ] = None - venue_package: Annotated[ - str | None, Field(description='Named collection of screens included in this buy') - ] = None - duration_hours: Annotated[ - float | None, - Field( - description='Duration of the DOOH slot in hours (e.g., 24 for a full-day takeover)', - ge=0.0, - ), - ] = None - daypart: Annotated[ - str | None, - Field(description='Named daypart for this slot (e.g., morning_commute, evening_rush)'), - ] = None - estimated_impressions: Annotated[ - int | None, - Field( - description='Estimated audience impressions for this slot (informational, not a delivery guarantee)', - ge=0, - ), - ] = None - - -class TimeUnit(StrEnum): - hour = 'hour' - day = 'day' - week = 'week' - month = 'month' - - -class Parameters3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - time_unit: Annotated[ - TimeUnit, - Field( - description='The time unit for pricing. Total cost = fixed_price × number of time_units in the campaign flight.' - ), - ] - min_duration: Annotated[ - int | None, Field(description='Minimum booking duration in time_units', ge=1) - ] = None - max_duration: Annotated[ - int | None, - Field( - description='Maximum booking duration in time_units. Must be >= min_duration when both are present.', - ge=1, - ), - ] = None - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class Dimensions1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' - placement_ref: Annotated[ - PlacementRef, - Field( - description="Structured placement reference for this forecast row. References an entry from the product's placements array.", - title='Placement Reference', - ), - ] - placement_name: Annotated[ - str | None, - Field( - description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' - ), - ] = None - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Presence(StrEnum): - present = 'present' - absent = 'absent' - - -class Dimensions5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class AudienceSize(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) - ] = None - - -class Reach(AudienceSize): - pass - - -class Frequency(AudienceSize): - pass - - -class Impressions(AudienceSize): - pass - - -class Clicks(AudienceSize): - pass - - -class Spend(AudienceSize): - pass - - -class Views(AudienceSize): - pass - - -class CompletedViews(AudienceSize): - pass - - -class Grps(AudienceSize): - pass - - -class Engagements(AudienceSize): - pass - - -class Follows(AudienceSize): - pass - - -class Saves(AudienceSize): - pass - - -class ProfileVisits(AudienceSize): - pass - - -class MeasuredImpressions(AudienceSize): - pass - - -class Downloads(AudienceSize): - pass - - -class Plays(AudienceSize): - pass - - -class CoverageRate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0) - ] = None - - -class Metrics(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate | None, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class MeasurableImpressions(AudienceSize): - pass - - -class ViewableImpressions(AudienceSize): - pass - - -class ViewableRate(CoverageRate): - pass - - -class ViewedSeconds(AudienceSize): - pass - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class Value(AudienceSize): - pass - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class OutcomeMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class AvailableRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[AvailableRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class Metric(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class NoticePeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class Type(StrEnum): - percent_remaining = 'percent_remaining' - full_commitment = 'full_commitment' - fixed_fee = 'fixed_fee' - none = 'none' - - -class CancellationFee(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type, - Field( - description="Fee calculation method. 'percent_remaining': percentage of remaining uncommitted spend. 'full_commitment': buyer owes the full committed budget regardless of delivery. 'fixed_fee': flat monetary amount. 'none': no financial fee (cancellation with notice is free)." - ), - ] - rate: Annotated[ - float | None, - Field( - description="Fee rate as a decimal proportion of remaining committed spend. Required when type is 'percent_remaining' (e.g., 0.5 means 50% of remaining spend).", - ge=0.0, - le=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Fixed fee amount in the buy's currency. Required when type is 'fixed_fee'.", - ge=0.0, - ), - ] = None - - -class CancellationPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee, Field(description='Fee applied when the notice period is not met.') - ] - - -class Action(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' - - -class Mode1(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - requires_approval = 'requires_approval' - - -class AllowedStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class Sla(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - response_max: Annotated[ - str | None, - Field( - description='Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration.', - examples=['PT5M', 'PT4H', 'P1D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - completion_max: Annotated[ - str | None, - Field( - description='Maximum time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, approval resolved). ISO 8601 duration.', - examples=['PT1H', 'PT24H', 'P2D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - - -class AllowedAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: Annotated[ - Action, Field(description='The action identifier.', title='Media Buy Valid Action') - ] - modes: Annotated[ - list[Mode1], - Field( - description='Modes available for this action on this product. A product may declare multiple modes (for example `self_serve` within tolerances, escalating to `requires_approval` outside) — the buy-side `available_actions[].mode` resolves to the singular mode in effect at mutation time. SDKs that see multiple modes MUST NOT assume which one will fire; they must read the resolved `mode` on the buy.', - min_length=1, - ), - ] - allowed_statuses: Annotated[ - list[AllowedStatus] | None, - Field( - description='Media buy statuses in which this action is permitted. When absent, the action is permitted in all non-terminal statuses (`pending_creatives`, `pending_start`, `active`, `paused`).', - min_length=1, - ), - ] = None - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this product. Absence means no commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). When present, the named term governs cancellation policy, makegoods, or other commercial remedies tied to this action. Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class ME(StrEnum): - zip = 'zip' - zip_plus_four = 'zip_plus_four' - - -class GBEnum(StrEnum): - outward = 'outward' - full = 'full' - - -class CAEnum(StrEnum): - fsa = 'fsa' - full = 'full' - - -class PostalArea(AdCPBaseModel): - US: Annotated[list[ME] | None, Field(min_length=1)] = None - GB: Annotated[list[GBEnum] | None, Field(min_length=1)] = None - CA: Annotated[list[CAEnum] | None, Field(min_length=1)] = None - DE: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - CH: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - AT: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - FR: Annotated[list[Literal['code_postal']] | None, Field(min_length=1)] = None - AU: Annotated[list[Literal['postcode']] | None, Field(min_length=1)] = None - BR: Annotated[list[Literal['cep']] | None, Field(min_length=1)] = None - IN: Annotated[list[Literal['pin']] | None, Field(min_length=1)] = None - ZA: Annotated[list[Literal['postal_code']] | None, Field(min_length=1)] = None - us_zip: Annotated[bool | None, Field(deprecated=True)] = None - us_zip_plus_four: Annotated[bool | None, Field(deprecated=True)] = None - gb_outward: Annotated[bool | None, Field(deprecated=True)] = None - gb_full: Annotated[bool | None, Field(deprecated=True)] = None - ca_fsa: Annotated[bool | None, Field(deprecated=True)] = None - ca_full: Annotated[bool | None, Field(deprecated=True)] = None - de_plz: Annotated[bool | None, Field(deprecated=True)] = None - fr_code_postal: Annotated[bool | None, Field(deprecated=True)] = None - au_postcode: Annotated[bool | None, Field(deprecated=True)] = None - ch_plz: Annotated[bool | None, Field(deprecated=True)] = None - at_plz: Annotated[bool | None, Field(deprecated=True)] = None - - -class SupportsGeoBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - -class DateRangeSupport(StrEnum): - date_range = 'date_range' - lifetime_only = 'lifetime_only' - - -class MeasurementWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - window_id: Annotated[ - str, - Field( - description="Identifier for this maturation stage. Standard broadcast values: 'live' (real-time viewers only), 'c3' (live + 3 days time-shifted), 'c7' (live + 7 days time-shifted). Standard values for other channels include 'tentative' (provisional data available quickly), 'final' (post-processing certified data), 'post_ivt' (digital after invalid-traffic filtering), 'post_sivt' (digital after sophisticated-IVT filtering), 'downloads_7d' / 'downloads_30d' (podcast download maturation). Sellers may define custom IDs.", - examples=['live', 'c3', 'c7', 'tentative', 'final', 'post_ivt', 'downloads_30d'], - max_length=50, - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of what this window measures', - examples=[ - 'Live broadcast impressions only', - 'Live plus 7 days of time-shifted viewing', - 'Tentative plays before IVT and fraud-check processing', - 'Final plays after IVT and fraud-check processing', - 'Impressions after sophisticated invalid-traffic filtering', - ], - max_length=500, - ), - ] = None - duration_days: Annotated[ - int, - Field( - description='Number of days of accumulation included in this window before processing begins. For broadcast, this is DVR accumulation (0 = live only, 3 = live + 3 days DVR, 7 = live + 7 days DVR). For channels without an accumulation period (DOOH tentative→final, digital IVT filtering), this is 0 — maturation is entirely vendor processing time captured in expected_availability_days.', - ge=0, - ), - ] - expected_availability_days: Annotated[ - int | None, - Field( - description="Expected number of days after delivery before this window's data is available from the measurement vendor. Captures accumulation time plus vendor processing time. Examples: broadcast C7 from VideoAmp ~22 days (7-day accumulation + ~15-day processing); DOOH tentative plays same-day; DOOH final (post-IVT/fraud-check) ~1 day; digital post-SIVT ~2–3 days.", - ge=0, - ), - ] = None - is_guarantee_basis: Annotated[ - bool | None, - Field( - description="Whether this window is the basis for delivery guarantees, reconciliation, and invoicing. A product typically has one guarantee basis window (e.g., C7 for most US broadcast, post-IVT final for DOOH). Buyers reconcile against the guarantee basis window's final numbers." - ), - ] = None - - -class CoBranding(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' - - -class LandingPage(StrEnum): - any = 'any' - retailer_site_only = 'retailer_site_only' - must_include_retailer = 'must_include_retailer' - - -class ProvenanceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - require_digital_source_type: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `digital_source_type` field in their provenance, set to a valid value from the `digital-source-type` enum (not null or absent). Submissions that omit this field are rejected with `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING`. Supports EU AI Act Art. 50 and CA SB 942 compliance workflows where AI disclosure metadata must be present at the protocol level.' - ), - ] = None - require_disclosure_metadata: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `disclosure` object in their provenance with `disclosure.required` set to a boolean value (true or false). When `disclosure.required` is true, at least one entry in `disclosure.jurisdictions` is expected. Submissions that omit `disclosure.required` are rejected with `PROVENANCE_DISCLOSURE_MISSING`.' - ), - ] = None - require_embedded_provenance: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include at least one `embedded_provenance` entry. For pipelines where sidecar metadata is stripped by intermediaries, this ensures provenance data persists through delivery. Submissions that omit `embedded_provenance` are rejected with `PROVENANCE_EMBEDDED_MISSING`.' - ), - ] = None - - -class AcceptedVerifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent. MUST use the `https://` scheme. The seller calls this URL via `get_creative_features` to verify a buyer's claim; the seller has already vetted the endpoint and accepts responsibility for outbound calls to it." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical `feature_id` the seller will request against this agent (e.g., `encypher.markers_present_v2`). When present, the buyer's `verify_agent.feature_id` SHOULD either match this value or be omitted. When absent, the seller selects a feature from the agent's `governance.creative_features` catalog at evaluation time. Resolves the selector ambiguity that would otherwise let two compliant receivers reach different verdicts." - ), - ] = None - providers: Annotated[ - list[str] | None, - Field( - description="Optional `provider` labels this agent verifies (e.g., `['Encypher', 'Digimarc']`). When present, sellers SHOULD only invoke this agent for `embedded_provenance[]` / `watermarks[]` entries whose `provider` field matches one of these labels — letting buyers pre-flight whether their attached evidence is verifiable against the seller's allowlist. When absent, the agent is treated as provider-agnostic.", - min_length=1, - ), - ] = None - - -class CreativePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - co_branding: Annotated[ - CoBranding, Field(description='Co-branding requirement', title='Co-Branding Requirement') - ] - landing_page: Annotated[ - LandingPage, - Field(description='Landing page requirements', title='Landing Page Requirement'), - ] - templates_available: Annotated[ - bool, Field(description='Whether creative templates are provided') - ] - provenance_required: Annotated[ - bool | None, - Field( - description='Whether creatives must include provenance metadata. When true, the seller requires buyers to attach provenance declarations to creative submissions. The seller may independently verify claims via get_creative_features.' - ), - ] = None - provenance_requirements: Annotated[ - ProvenanceRequirements | None, - Field( - description='Structured provenance requirements for creatives. Refines `provenance_required`: when `provenance_required` is true, the fields in this object specify which provenance features the seller requires. When `provenance_required` is false or absent, this object SHOULD be absent; if present, receivers MUST ignore it. Existing seller agents that do not read this object are unaffected; the wire shape does not change for them. Sellers that publish a requirement here MUST enforce it on creative submission: a `sync_creatives` request that omits a required field is rejected with the corresponding `PROVENANCE_*` error code (see error-code.json), and a creative whose provenance claim is contradicted by an independent verification (`get_creative_features` against a governance agent the seller operates or has allowlisted via `accepted_verifiers`) is rejected with `PROVENANCE_CLAIM_CONTRADICTED`. This is the structural-rejection surface; the truth-of-claim surface lives in `get_creative_features`. Field-level requirements are seller-enforced — JSON Schema validation does not check them.' - ), - ] = None - accepted_verifiers: Annotated[ - list[AcceptedVerifier] | None, - Field( - description='Governance agents the seller operates, has allowlisted, or otherwise trusts to verify provenance claims via `get_creative_features`. Buyers attaching a `verify_agent` pointer on `embedded_provenance[]` or `watermarks[]` MUST select an `agent_url` that appears in this list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) - the buyer is *representing* that they used a verifier the seller will recognize, not asserting unilateral routing. Sellers MUST reject `sync_creatives` submissions whose `verify_agent.agent_url` does not match any entry here with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call. Publishing the list lets buyers pre-flight their creative shape against `get_products` and lets multiple buyers converge on the same verifier without coordinating with each other.', - min_length=1, - ), - ] = None - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[float, Field(description='Minimum value, inclusive.')] - max: Annotated[float, Field(description='Maximum value, inclusive.')] - - - - - - -class ActivationStatus(StrEnum): - ready = 'ready' - requires_activation = 'requires_activation' - - -class AllowedTargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption6(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption7(PricingOption1, PricingOption6): - pass - - -class PricingOption8(PricingOption2, PricingOption6): - pass - - -class PricingOption9(PricingOption3, PricingOption6): - pass - - -class PricingOption10(PricingOption4, PricingOption6): - pass - - -class PricingOption11(PricingOption5, PricingOption6): - pass - - -class PricingOption( - RootModel[PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11] -): - root: Annotated[ - PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ResolutionModel(StrEnum): - direct_targeting = 'direct_targeting' - seller_planned = 'seller_planned' - - -class SelectionMode(StrEnum): - optional = 'optional' - required = 'required' - fixed = 'fixed' - - -class SelectionGroupRule(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - selection_group: Annotated[ - str, - Field( - description='ProductSignalTargetingOption.selection_group value this rule applies to.' - ), - ] - targeting_mode: Annotated[ - AllowedTargetingMode | None, - Field( - description="How options in this selection_group are intended to be used in signal_targeting_groups. 'include' maps to child groups with operator 'any'. 'exclude' maps to child groups with operator 'none'. Omit when options in the group may be used according to each option's allowed_targeting_modes." - ), - ] = None - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Selection behavior for this selection_group. 'required' means at least min_selected_signals, or 1 when omitted. 'fixed' means default_selected options in this group are seller-applied and read-only." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum selected options from this selection_group. If selection_mode is 'required' and omitted, sellers MUST treat the minimum as 1.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, Field(description='Maximum selected options from this selection_group.', ge=1) - ] = None - - -class SignalTargetingRules(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class SupportedMetric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class SupportedViewDuration(RootModel[float]): - root: Annotated[float, Field(gt=0.0)] - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class Status25(StrEnum): - insufficient = 'insufficient' - minimum = 'minimum' - good = 'good' - excellent = 'excellent' - - -class Severity(StrEnum): - error = 'error' - warning = 'warning' - info = 'info' - - -class Issue1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - severity: Annotated[ - Severity, - Field( - description="'error': blocks optimization until resolved. 'warning': optimization works but effectiveness is reduced. 'info': suggestion for improvement." - ), - ] - message: Annotated[ - str, - Field(description='Human/agent-readable description of the issue and how to resolve it.'), - ] - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class SupportedTarget2(StrEnum): - cost_per = 'cost_per' - per_ad_spend = 'per_ad_spend' - maximize_value = 'maximize_value' - - -class ConversionTracking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_sources: Annotated[ - list[ActionSource] | None, - Field( - description="Action sources relevant to this product (e.g. a retail media product might have 'in_store' and 'website', while a display product might only have 'website')", - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget2] | None, - Field( - description='Target kinds available for event goals on this product. Values match target.kind on the optimization goal. cost_per: target cost per conversion event. per_ad_spend: target return on ad spend (requires value_field on event sources). maximize_value: maximize total conversion value without a specific ratio target (requires value_field). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes conversion count within budget — no declaration needed for that mode. When omitted, buyers can still set target-less event goals.', - min_length=1, - ), - ] = None - platform_managed: Annotated[ - bool | None, - Field( - description="Whether the seller provides its own always-on measurement (e.g. Amazon sales attribution for Amazon advertisers). When true, sync_event_sources response will include seller-managed event sources with managed_by='seller'." - ), - ] = None - - -class MatchedGtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class CatalogMatch(AdCPBaseModel): - matched_gtins: Annotated[ - list[MatchedGtin] | None, - Field( - description="GTINs from the buyer's catalog that are eligible on this product's inventory. Standard GTIN formats (GTIN-8 through GTIN-14). Only present for product-type catalogs with GTIN matching." - ), - ] = None - matched_ids: Annotated[ - list[str] | None, - Field( - description="Item IDs from the buyer's catalog that matched this product's inventory. The ID type depends on the catalog type and content_id_type (e.g., SKUs for product catalogs, job_ids for job catalogs, offering_ids for offering catalogs)." - ), - ] = None - matched_count: Annotated[ - int | None, - Field(description="Number of catalog items that matched this product's inventory.", ge=0), - ] = None - submitted_count: Annotated[ - int, Field(description="Total catalog items evaluated from the buyer's catalog.", ge=0) - ] - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class Specification(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[str, Field(max_length=60)] - value: Annotated[str, Field(max_length=200)] - - -class Collection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_ids: Annotated[ - list[str], - Field( - description='Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.', - min_length=1, - ), - ] - - -class Status26(StrEnum): - scheduled = 'scheduled' - tentative = 'tentative' - live = 'live' - postponed = 'postponed' - cancelled = 'cancelled' - aired = 'aired' - published = 'published' - - -class System(StrEnum): - tv_parental = 'tv_parental' - mpaa = 'mpaa' - podcast = 'podcast' - esrb = 'esrb' - bbfc = 'bbfc' - fsk = 'fsk' - acb = 'acb' - chvrs = 'chvrs' - csa = 'csa' - pegi = 'pegi' - custom = 'custom' - - -class ContentRating(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: Annotated[ - System, Field(description='Rating system used', title='Content Rating System') - ] - rating: Annotated[ - str, Field(description="Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") - ] - - -class Category(StrEnum): - awards = 'awards' - championship = 'championship' - concert = 'concert' - conference = 'conference' - election = 'election' - festival = 'festival' - gala = 'gala' - holiday = 'holiday' - premiere = 'premiere' - product_launch = 'product_launch' - reunion = 'reunion' - tribute = 'tribute' - - -class Special(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, Field(description="Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')") - ] - category: Annotated[ - Category | None, Field(description='Category of the event', title='Special Category') - ] = None - starts: Annotated[ - AwareDatetime | None, Field(description='When the event starts (ISO 8601)') - ] = None - ends: Annotated[ - AwareDatetime | None, - Field(description='When the event ends (ISO 8601). Omit for single-day events.'), - ] = None - - -class Role10(StrEnum): - host = 'host' - guest = 'guest' - creator = 'creator' - cast = 'cast' - narrator = 'narrator' - producer = 'producer' - correspondent = 'correspondent' - commentator = 'commentator' - analyst = 'analyst' - - -class GuestTalentItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - Role10, - Field( - description='Role of this person on the collection or installment', title='Talent Role' - ), - ] - name: Annotated[str, Field(description="Person's name as credited on the collection")] - brand_url: Annotated[ - AnyUrl | None, - Field( - description="URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations." - ), - ] = None - - -class AdInventory(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - expected_breaks: Annotated[ - int, Field(description='Number of planned ad breaks in the installment', ge=0) - ] - total_ad_seconds: Annotated[ - int | None, Field(description='Total seconds of ad time across all breaks', ge=0) - ] = None - max_ad_duration_seconds: Annotated[ - int | None, - Field( - description='Maximum duration in seconds for a single ad within a break. Buyers need this to know whether their creative fits.', - ge=1, - ), - ] = None - unplanned_breaks: Annotated[ - bool | None, - Field( - description='Whether ad breaks are dynamic and driven by live conditions (sports timeouts, election coverage). When false, all breaks are pre-defined.' - ), - ] = None - supported_formats: Annotated[ - list[str] | None, - Field( - description="Ad format types supported in breaks (e.g., 'video', 'audio', 'display')" - ), - ] = None - - -class MaterialDeadline(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - stage: Annotated[ - str, - Field( - description="Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", - examples=['draft', 'final'], - ), - ] - due_at: Annotated[ - AwareDatetime, Field(description='When materials for this stage are due (ISO 8601)') - ] - label: Annotated[ - str | None, - Field( - description="What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" - ), - ] = None - - -class Deadlines(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - booking_deadline: Annotated[ - AwareDatetime | None, - Field( - description='Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings.' - ), - ] = None - cancellation_deadline: Annotated[ - AwareDatetime | None, - Field( - description="Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." - ), - ] = None - material_deadlines: Annotated[ - list[MaterialDeadline] | None, - Field( - description="Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", - min_length=1, - ), - ] = None - - -class Type1(StrEnum): - clip = 'clip' - highlight = 'highlight' - recap = 'recap' - trailer = 'trailer' - bonus = 'bonus' - - -class DerivativeOf(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - installment_id: Annotated[ - str, Field(description='The source installment this content is derived from') - ] - type: Annotated[ - Type1, Field(description='What kind of derivative content this is', title='Derivative Type') - ] - - -class Installment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: Annotated[ - Status26 | None, - Field(description='Lifecycle status of the installment', title='Installment Status'), - ] = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class ResponseType(StrEnum): - activation = 'activation' - catalog_items = 'catalog_items' - creative = 'creative' - deal = 'deal' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class UidType(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="Provider's agent URL from the registry. Canonical identifier for this TMP provider." - ), - ] - context_match: Annotated[ - bool | None, - Field(description='Whether this provider handles context match for this product.'), - ] = False - identity_match: Annotated[ - bool | None, - Field(description='Whether this provider handles identity match for this product.'), - ] = False - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves for identity match. The router uses this to select the correct regional provider based on the request's country field. Required when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[UidType] | None, - Field( - description="Identity types this regional provider can resolve. The router filters providers whose uid_types includes the request's uid_type. Required when identity_match is true.", - min_length=1, - ), - ] = None - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[ResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [ResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class MaterialSubmission(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field(description='HTTPS URL for uploading or submitting physical creative materials'), - ] = None - email: Annotated[ - EmailStr | None, Field(description='Email address for creative material submission') - ] = None - instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for material submission (file naming conventions, shipping address, etc.)', - max_length=2000, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PublisherProperty81(PublisherProperty1): - pass - - -class PublisherProperty82(PublisherProperty2): - pass - - -class PublisherProperty83(PublisherProperty3): - pass - - -class PublisherProperty84(PublisherProperty4): - pass - - -class PublisherProperty85(PublisherProperty81, PublisherProperty84): - pass - - -class PublisherProperty86(PublisherProperty82, PublisherProperty84): - pass - - -class PublisherProperty87(PublisherProperty83, PublisherProperty84): - pass - - -class PublisherProperty8( - RootModel[PublisherProperty85 | PublisherProperty86 | PublisherProperty87] -): - root: PublisherProperty85 | PublisherProperty86 | PublisherProperty87 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection24(RequiredConnection): - pass - - -class RequiredConnection25(RequiredConnection): - pass - - -class RequiredConnection26(RequiredConnection): - pass - - -class RequiredConnection27(RequiredConnection): - pass - - -class RequiredConnection28(RequiredConnection): - pass - - -class RequiredConnection29(RequiredConnection): - pass - - -class RequiredConnection30(RequiredConnection): - pass - - -class RequiredConnection31(RequiredConnection): - pass - - -class RequiredConnection32(RequiredConnection): - pass - - -class RequiredConnection33(RequiredConnection): - pass - - -class RequiredConnection34(RequiredConnection): - pass - - -class RequiredConnection35(RequiredConnection): - pass - - -class RequiredConnection36(RequiredConnection): - pass - - -class RequiredConnection37(RequiredConnection): - pass - - -class RequiredConnection38(RequiredConnection): - pass - - -class RequiredConnection39(RequiredConnection): - pass - - -class RequiredConnection40(RequiredConnection): - pass - - -class RequiredConnection41(RequiredConnection): - pass - - -class RequiredConnection42(RequiredConnection): - pass - - -class RequiredConnection43(RequiredConnection): - pass - - -class RequiredConnection44(RequiredConnection): - pass - - -class RequiredConnection45(RequiredConnection): - pass - - -class RequiredConnection46(RequiredConnection): - pass - - -class RequiredConnection47(RequiredConnection): - pass - - -class ViewThreshold2(ViewThreshold): - pass - - -class ViewThreshold3(ViewThreshold1): - pass - - -class Parameters4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold2 | ViewThreshold3 - - -class Parameters6(Parameters2): - pass - - -class Parameters7(Parameters3): - pass - - -class Dimensions7(Dimensions1): - pass - - - - -class Dimensions11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics1(Metrics): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class Window1(Window): - pass - - -class OutcomeMeasurement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window1 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class MakegoodPolicy1(MakegoodPolicy): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class NoticePeriod1(NoticePeriod): - pass - - -class CancellationFee1(CancellationFee): - pass - - -class CancellationPolicy1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod1, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee1, Field(description='Fee applied when the notice period is not met.') - ] - - -class AllowedAction1(AllowedAction): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class PostalArea1(PostalArea): - pass - - -class SupportsGeoBreakdown1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea1 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - -class CreativePolicy1(CreativePolicy): - pass - - - - - - - - - - - -class PricingOption121(PricingOption1): - pass - - -class PricingOption122(PricingOption2): - pass - - -class PricingOption123(PricingOption3): - pass - - -class PricingOption124(PricingOption4): - pass - - -class PricingOption125(PricingOption5): - pass - - -class PricingOption126(PricingOption6): - pass - - -class PricingOption127(PricingOption121, PricingOption126): - pass - - -class PricingOption128(PricingOption122, PricingOption126): - pass - - -class PricingOption129(PricingOption123, PricingOption126): - pass - - -class PricingOption1210(PricingOption124, PricingOption126): - pass - - -class PricingOption1211(PricingOption125, PricingOption126): - pass - - -class PricingOption12( - RootModel[ - PricingOption127 - | PricingOption128 - | PricingOption129 - | PricingOption1210 - | PricingOption1211 - ] -): - root: Annotated[ - PricingOption127 - | PricingOption128 - | PricingOption129 - | PricingOption1210 - | PricingOption1211, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule1(SelectionGroupRule): - pass - - -class SignalTargetingRules1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule1] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class Issue2(Issue1): - pass - - -class ConversionTracking1(ConversionTracking): - pass - - -class CatalogMatch1(CatalogMatch): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class ContentRating1(ContentRating): - pass - - -class Special1(Special): - pass - - -class GuestTalentItem1(GuestTalentItem): - pass - - -class Deadlines1(Deadlines): - pass - - -class DerivativeOf1(DerivativeOf): - pass - - -class Installment1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: Annotated[ - Status26 | None, - Field(description='Lifecycle status of the installment', title='Installment Status'), - ] = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating1 | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special1 | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem1] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines1 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf1 | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider1(Provider): - pass - - -class TrustedMatch1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[ResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [ResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider1] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class Extensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - extends: Annotated[ - str, - Field( - description='Canonical concept this extension extends (e.g., `tracking`, `cta_vocabulary`, `destinations`, `placement`).' - ), - ] - fields: Annotated[ - dict[str, Any], - Field( - description='JSON Schema fragment declaring the additional fields this extension contributes.' - ), - ] - version: Annotated[ - str | None, - Field( - description='Semantic version of the extension definition. Distinct from the digest — version is human-readable; digest is the integrity check.' - ), - ] = None - description: str | None = None - - -class Day(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[Day], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Dimensions13(Dimensions1): - pass - - - - -class Dimensions17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics2(Metrics): - pass - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - - -class TotalBudget(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[ - str, Field(description='ISO 4217 currency code', max_length=3, min_length=3) - ] - - -class PaymentTerms(StrEnum): - net_30 = 'net_30' - net_60 = 'net_60' - net_90 = 'net_90' - prepaid = 'prepaid' - due_on_receipt = 'due_on_receipt' - - -class Terms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - advertiser: Annotated[ - str | None, Field(description='Advertiser name or identifier', max_length=500) - ] = None - publisher: Annotated[ - str | None, Field(description='Publisher name or identifier', max_length=500) - ] = None - total_budget: Annotated[TotalBudget | None, Field(description='Total committed budget')] = None - flight_start: Annotated[AwareDatetime | None, Field(description='Campaign start date')] = None - flight_end: Annotated[AwareDatetime | None, Field(description='Campaign end date')] = None - payment_terms: Annotated[PaymentTerms | None, Field(description='Payment terms')] = None - - -class InsertionOrder(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, - Field( - description='Unique identifier for this insertion order. Referenced by io_acceptance on create_media_buy.', - max_length=255, - ), - ] - terms: Annotated[ - Terms | None, - Field( - description='Summary fields echoed from the committed proposal for agent verification. Buyer agents use these to confirm the IO matches what was negotiated before a human signs. These are read-only summaries, not negotiation surfaces — deal terms live on products and packages.' - ), - ] = None - terms_url: Annotated[ - AnyUrl | None, - Field( - description='URL to a human-readable document containing the full insertion order terms' - ), - ] = None - signing_url: Annotated[ - AnyUrl | None, - Field( - description='URL to an electronic signing service (e.g., DocuSign) for human signature workflows. When present, a human must sign before the buyer agent can proceed with create_media_buy.' - ), - ] = None - requires_signature: Annotated[ - bool, - Field( - description='Whether the buyer must accept this IO before creating a media buy. When true, create_media_buy requires an io_acceptance referencing this io_id.' - ), - ] - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[float | None, Field(description='Minimum recommended budget', ge=0.0)] = None - recommended: Annotated[ - float | None, Field(description='Recommended budget for optimal performance', ge=0.0) - ] = None - max: Annotated[ - float | None, Field(description='Maximum budget before diminishing returns', ge=0.0) - ] = None - currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None - - -class Dimensions19(Dimensions1): - pass - - - - -class Dimensions23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics3(Metrics): - pass - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class Issue3(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue3] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status53(StrEnum): - applied = 'applied' - partial = 'partial' - unable = 'unable' - - -class RefinementApplied1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field(description="Echoes scope 'request' from the corresponding refine entry."), - ] = 'request' - status: Annotated[ - Status53, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['product'], - Field(description="Echoes scope 'product' from the corresponding refine entry."), - ] = 'product' - product_id: Annotated[ - str, Field(description='Echoes product_id from the corresponding refine entry.') - ] - status: Annotated[ - Status53, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], - Field(description="Echoes scope 'proposal' from the corresponding refine entry."), - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Echoes proposal_id from the corresponding refine entry.') - ] - status: Annotated[ - Status53, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied(RootModel[RefinementApplied1 | RefinementApplied2 | RefinementApplied3]): - root: Annotated[ - RefinementApplied1 | RefinementApplied2 | RefinementApplied3, Field(discriminator='scope') - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Scope48(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - proposals = 'proposals' - wholesale_feed = 'wholesale_feed' - - -class EstimatedWait(Window): - pass - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope48, - Field( - description="'products': not all inventory sources were searched. 'pricing': products returned but pricing is absent or unconfirmed. 'forecast': products returned but forecast data is absent. 'proposals': proposals were not generated or are incomplete. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget — symmetric with get_signals' 'wholesale_feed' scope so sellers have a precise way to declare wholesale-incomplete on the products surface." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait | None, - Field( - description='How much additional time would resolve this scope. Allows the buyer to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class Semantics(StrEnum): - only = 'only' - any = 'any' - approximate = 'approximate' - - -class ExcludedBy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - count: Annotated[ - int, - Field( - description='Number of products excluded by this filter, interpreted per the parent `semantics` field.', - ge=0, - ), - ] - values: Annotated[ - list[str | dict[str, Any]] | None, - Field( - description='Optional list of the specific filter values that contributed to exclusions, when meaningful. For `required_metrics`: the metric names that excluded products (strings). For `required_vendor_metrics`: the vendor/metric pin entries (objects). Item shape is filter-specific; the schema admits string OR object items. Buyers without filter-specific knowledge SHOULD treat as opaque.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Optional human-readable note about why this filter narrowed the set (e.g., 'no products in this brief support DV viewability at the requested threshold')." - ), - ] = None - - -class FilterDiagnostics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - semantics: Annotated[ - Semantics | None, - Field( - description="How `excluded_by[*].count` values are computed across multiple filters. `only`: counts products that would have been included if not for THIS filter alone (deterministic; the right value for 'which filter killed my result set' triage — recommended when feasible). `any`: counts products excluded by ANY filter (so multiple filters' counts may overlap and sum to more than `total_candidates`). `approximate`: sellers SHOULD use this when their pipeline can't cleanly attribute exclusions to a single filter. Buyers SHOULD inspect `semantics` before doing arithmetic on counts." - ), - ] = None - total_candidates: Annotated[ - int | None, - Field( - description='Number of products the seller considered before applying `filters`. Baseline for interpreting per-filter exclusion counts. Approximate — sellers MAY return a sampled or capped count when their candidate pool is large. Optional; sellers whose baseline candidate set size is sensitive (revealing market posture or competitive density) MAY omit this while still emitting `excluded_by`.', - ge=0, - ), - ] = None - excluded_by: Annotated[ - dict[str, ExcludedBy] | None, - Field( - description="Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request)." - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' - - -class VideoPlacementType(StrEnum): - instream = 'instream' - accompanying_content = 'accompanying_content' - interstitial = 'interstitial' - standalone = 'standalone' - - -class AudioDistributionType(StrEnum): - music_streaming_service = 'music_streaming_service' - fm_am_broadcast = 'fm_am_broadcast' - podcast = 'podcast' - catch_up_radio = 'catch_up_radio' - web_radio = 'web_radio' - video_game = 'video_game' - text_to_speech = 'text_to_speech' - - -class SponsoredPlacementType(StrEnum): - sponsored_search = 'sponsored_search' - sponsored_display = 'sponsored_display' - sponsored_native = 'sponsored_native' - - -class SocialPlacementSurface(StrEnum): - feed = 'feed' - stories = 'stories' - short_video = 'short_video' - explore = 'explore' - search = 'search' - - -class PriceAdjustmentKind(StrEnum): - fee = 'fee' - discount = 'discount' - commission = 'commission' - settlement = 'settlement' - - -class DemographicSystem(StrEnum): - nielsen = 'nielsen' - barb = 'barb' - agf = 'agf' - oztam = 'oztam' - mediametrie = 'mediametrie' - custom = 'custom' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class GeographicTargetingLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class PostalCodeSystem(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class ForecastRangeUnit(StrEnum): - spend = 'spend' - availability = 'availability' - reach_freq = 'reach_freq' - weekly = 'weekly' - daily = 'daily' - clicks = 'clicks' - conversions = 'conversions' - package = 'package' - - -class ForecastMethod(StrEnum): - estimate = 'estimate' - modeled = 'modeled' - guaranteed = 'guaranteed' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class SignalValueType(StrEnum): - binary = 'binary' - categorical = 'categorical' - numeric = 'numeric' - - -class DataProviderSignalSelector1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all signals from this data provider are included' - ), - ] = 'all' - - -class SignalId8(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-zA-Z0-9_-]+$')] - - -class DataProviderSignalSelector2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific signal IDs'), - ] = 'by_id' - signal_ids: Annotated[ - list[SignalId8], - Field( - description="Specific signal IDs from the data provider's published signal definitions", - min_length=1, - ), - ] - - -class SignalTag(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9_-]+$')] - - -class DataProviderSignalSelector3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by signal tags') - ] = 'by_tag' - signal_tags: Annotated[ - list[SignalTag], - Field( - description="Signal tags from the data provider's published signal definitions. Selector covers all signals with these tags", - min_length=1, - ), - ] - - -class DataProviderSignalSelector( - RootModel[ - DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3 - ] -): - root: Annotated[ - DataProviderSignalSelector1 | DataProviderSignalSelector2 | DataProviderSignalSelector3, - Field( - description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", - discriminator='selection_type', - title='Data Provider Signal Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType, - Field( - description="Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." - ), - ] - required: Annotated[ - bool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - int | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - int | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - int | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - int | None, - Field( - description="Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot1(Slot): - pass - - -class Params1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot1] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection1] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params1, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot2(Slot): - pass - - -class Params2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot2] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection2] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params2, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot3(Slot): - pass - - -class Params3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot3] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection3] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params3, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot4(Slot): - pass - - -class Params4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot4] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection4] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params4, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot5(Slot): - pass - - -class Params5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot5] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection5] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params5, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot6(Slot): - pass - - -class Params6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot6] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection6] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec1] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params6, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot7(Slot): - pass - - -class Params7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot7] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection7] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params7, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot8(Slot): - pass - - -class Params8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot8] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection8] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params8, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot9(Slot): - pass - - -class Params9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot9] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection9] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource3 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource3.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params9, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot10(Slot): - pass - - -class Params10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot10] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection10] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params10, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot11(Slot): - pass - - -class Params11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot11] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection11] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params11, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class FormatOptions( - RootModel[ - FormatOptions1 - | FormatOptions2 - | FormatOptions3 - | FormatOptions4 - | FormatOptions5 - | FormatOptions6 - | FormatOptions7 - | FormatOptions8 - | FormatOptions9 - | FormatOptions10 - | FormatOptions11 - | FormatOptions12 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions1 - | FormatOptions2 - | FormatOptions3 - | FormatOptions4 - | FormatOptions5 - | FormatOptions6 - | FormatOptions7 - | FormatOptions8 - | FormatOptions9 - | FormatOptions10 - | FormatOptions11 - | FormatOptions12 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot12(Slot): - pass - - -class Params12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot12] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection12] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params12, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot13(Slot): - pass - - -class Params13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot13] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection13] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params13, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot14(Slot): - pass - - -class Params14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot14] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection14] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions17(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params14, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot15(Slot): - pass - - -class Params15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot15] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection15] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions18(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params15, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot16(Slot): - pass - - -class Params16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot16] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection16] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions19(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params16, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot17(Slot): - pass - - -class Params17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot17] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection17] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions20(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params17, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot18(Slot): - pass - - -class Params18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot18] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection18] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec1] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions21(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params18, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot19(Slot): - pass - - -class Params19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot19] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection19] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions22(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params19, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot20(Slot): - pass - - -class Params20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot20] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection20] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions23(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params20, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot21(Slot): - pass - - -class Params21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot21] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection21] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource3 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource3.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions24(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params21, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot22(Slot): - pass - - -class Params22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot22] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection22] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions25(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params22, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot23(Slot): - pass - - -class Params23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot23] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection23] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions26(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params23, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions14( - RootModel[ - FormatOptions15 - | FormatOptions16 - | FormatOptions17 - | FormatOptions18 - | FormatOptions19 - | FormatOptions20 - | FormatOptions21 - | FormatOptions22 - | FormatOptions23 - | FormatOptions24 - | FormatOptions25 - | FormatOptions26 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions15 - | FormatOptions16 - | FormatOptions17 - | FormatOptions18 - | FormatOptions19 - | FormatOptions20 - | FormatOptions21 - | FormatOptions22 - | FormatOptions23 - | FormatOptions24 - | FormatOptions25 - | FormatOptions26 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - Sequence[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions14] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class Adjustment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: PriceAdjustmentKind - name: Annotated[ - str, - Field( - description='Specific adjustment name. Use well-known values where applicable for interoperability.', - examples=[ - 'ad_serving', - 'data_targeting', - 'brand_safety', - 'volume', - 'negotiated', - 'early_booking', - 'agency', - 'intermediary', - 'cash_discount', - 'early_payment', - ], - max_length=64, - ), - ] - rate: Annotated[ - float | None, - Field( - description='Adjustment as a decimal proportion (e.g., 0.15 for 15%). Always positive — kind determines the economic effect. Mutually exclusive with amount.', - gt=0.0, - lt=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Adjustment as a fixed monetary amount in the pricing option's currency. Always positive — kind determines the economic effect. Mutually exclusive with rate.", - gt=0.0, - ), - ] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this adjustment (e.g., 'Malstaffel 12x', '2% Skonto 10 Tage')", - max_length=256, - ), - ] = None - beneficiary: Annotated[ - str | None, - Field( - description="Identifies who receives this adjustment's value. For commissions, the intermediary (e.g., a sellers.json domain, an AdCP account ID, or a human-readable party name). Optional but recommended for multi-intermediary transparency.", - max_length=256, - ), - ] = None - - -class PriceBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - list_price: Annotated[ - float, - Field( - description='Rate card or base price before any adjustments. The starting point from which fixed_price is derived by applying fee and discount adjustments sequentially.', - gt=0.0, - ), - ] - adjustments: Annotated[ - list[Adjustment], - Field( - description="Ordered list of price adjustments. Fee and discount adjustments walk list_price to fixed_price — fees increase the running price, discounts reduce it. Commission and settlement adjustments are disclosed for transparency but do not affect the buyer's committed price.", - max_length=20, - min_length=1, - ), - ] - - -class PricingOptions1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown1(PriceBreakdown): - pass - - -class PricingOptions2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown1 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown2(PriceBreakdown): - pass - - -class PricingOptions3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown2 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown3(PriceBreakdown): - pass - - -class PricingOptions4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown3 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown4(PriceBreakdown): - pass - - -class PricingOptions5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown4 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class Parameters1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str, - Field( - description='Target demographic code within the specified demographic_system (e.g., P18-49 for Nielsen, ABC1 Adults for BARB)' - ), - ] - min_points: Annotated[float | None, Field(description='Minimum GRPs/TRPs required', ge=0.0)] = ( - None - ) - - -class PriceBreakdown5(PriceBreakdown): - pass - - -class PricingOptions6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters1, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown5 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown6(PriceBreakdown): - pass - - -class PricingOptions7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown6 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown7(PriceBreakdown): - pass - - -class PricingOptions8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters2 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown7 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown8(PriceBreakdown): - pass - - -class PricingOptions9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters3, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown8 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions( - RootModel[ - PricingOptions1 - | PricingOptions2 - | PricingOptions3 - | PricingOptions4 - | PricingOptions5 - | PricingOptions6 - | PricingOptions7 - | PricingOptions8 - | PricingOptions9 - ] -): - root: Annotated[ - PricingOptions1 - | PricingOptions2 - | PricingOptions3 - | PricingOptions4 - | PricingOptions5 - | PricingOptions6 - | PricingOptions7 - | PricingOptions8 - | PricingOptions9, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' - geo_level: GeographicTargetingLevel - system: Annotated[ - str | None, - Field( - description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." - ), - ] = None - - -class Dimensions2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' - device_type: DeviceType - - -class Dimensions3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['device_platform'], Field(description='Dimension family discriminator.') - ] = 'device_platform' - device_platform: DevicePlatform - - -class Dimensions4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' - audience_id: Annotated[ - str, Field(description='Audience segment identifier for this forecast row.') - ] - audience_source: AudienceSource - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name.') - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor1, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[Dimensions | Dimensions1 | Dimensions2 | Dimensions3 | Dimensions4 | Dimensions5] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement(AdCPBaseModel): - vendors: Annotated[ - list[Vendor2] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor3, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo4 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride4 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor4, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo5 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride5 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor5, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class IncludedSignal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric], - Field( - description='Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here. **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — declare vendor-attested attention/quality metrics via `vendor_metric_optimization.supported_metrics[]` with an explicit vendor binding instead. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind.', - min_length=1, - ), - ] - supported_reach_units: Annotated[ - list[ReachUnit] | None, - Field( - description="Reach units this product can optimize for. Required when supported_metrics includes 'reach'. Buyers must set reach_unit to a value in this list on reach optimization goals — sellers reject unsupported values.", - min_length=1, - ), - ] = None - supported_view_durations: Annotated[ - list[SupportedViewDuration] | None, - Field( - description="Video view duration thresholds (in seconds) this product supports for completed_views goals. Only relevant when supported_metrics includes 'completed_views'. When absent, the seller uses their platform default. Buyers must set view_duration_seconds to a value in this list — sellers reject unsupported values." - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for metric goals on this product. Values match target.kind on the optimization goal. Only these target kinds are accepted — goals with unlisted target kinds will be rejected. When omitted, buyers can set target-less metric goals (maximize volume within budget) but cannot set specific targets.' - ), - ] = None - - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo6 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride6 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor6, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric1], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status25, - Field( - description="Overall measurement readiness level for this product given the buyer's event setup. 'insufficient' means the product cannot optimize effectively with the current setup.", - title='Assessment Status', - ), - ] - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue1] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Products(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId], - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] - format_options: Annotated[ - list[FormatOptions] | None, - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[Placement] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - DeliveryType, Field(description='Type of inventory delivery', title='Delivery Type') - ] - exclusivity: Annotated[ - Exclusivity | None, - Field( - description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements.", - title='Exclusivity', - ), - ] = None - pricing_options: Annotated[ - list[PricingOptions], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot24(Slot): - pass - - -class Params24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot24] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection24] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions29(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params24, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot25(Slot): - pass - - -class Params25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot25] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection25] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions30(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params25, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot26(Slot): - pass - - -class Params26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot26] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection26] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions31(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params26, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot27(Slot): - pass - - -class Params27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot27] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection27] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions32(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params27, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot28(Slot): - pass - - -class Params28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot28] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection28] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions33(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params28, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot29(Slot): - pass - - -class Params29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot29] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection29] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions34(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params29, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot30(Slot): - pass - - -class Params30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot30] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection30] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec1] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions35(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params30, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot31(Slot): - pass - - -class Params31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot31] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection31] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions36(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params31, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot32(Slot): - pass - - -class Params32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot32] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection32] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions37(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params32, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot33(Slot): - pass - - -class Params33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot33] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection33] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource3 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource3.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions38(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params33, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot34(Slot): - pass - - -class Params34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot34] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection34] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions39(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params34, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot35(Slot): - pass - - -class Params35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot35] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection35] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions40(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params35, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions28( - RootModel[ - FormatOptions29 - | FormatOptions30 - | FormatOptions31 - | FormatOptions32 - | FormatOptions33 - | FormatOptions34 - | FormatOptions35 - | FormatOptions36 - | FormatOptions37 - | FormatOptions38 - | FormatOptions39 - | FormatOptions40 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions29 - | FormatOptions30 - | FormatOptions31 - | FormatOptions32 - | FormatOptions33 - | FormatOptions34 - | FormatOptions35 - | FormatOptions36 - | FormatOptions37 - | FormatOptions38 - | FormatOptions39 - | FormatOptions40 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot36(Slot): - pass - - -class Params36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot36] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection36] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions43(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params36, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot37(Slot): - pass - - -class Params37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot37] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection37] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions44(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params37, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot38(Slot): - pass - - -class Params38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot38] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection38] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions45(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params38, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot39(Slot): - pass - - -class Params39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot39] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection39] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions46(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params39, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot40(Slot): - pass - - -class Params40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot40] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection40] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions47(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params40, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot41(Slot): - pass - - -class Params41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot41] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection41] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions48(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params41, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot42(Slot): - pass - - -class Params42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot42] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection42] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec1] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions49(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params42, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot43(Slot): - pass - - -class Params43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot43] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection43] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions50(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params43, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot44(Slot): - pass - - -class Params44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot44] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection44] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions51(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params44, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot45(Slot): - pass - - -class Params45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot45] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection45] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource3 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource3.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions52(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params45, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot46(Slot): - pass - - -class Params46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot46] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection46] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions53(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params46, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot47(Slot): - pass - - -class Params47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot47] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection47] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions54(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params47, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions42( - RootModel[ - FormatOptions43 - | FormatOptions44 - | FormatOptions45 - | FormatOptions46 - | FormatOptions47 - | FormatOptions48 - | FormatOptions49 - | FormatOptions50 - | FormatOptions51 - | FormatOptions52 - | FormatOptions53 - | FormatOptions54 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions43 - | FormatOptions44 - | FormatOptions45 - | FormatOptions46 - | FormatOptions47 - | FormatOptions48 - | FormatOptions49 - | FormatOptions50 - | FormatOptions51 - | FormatOptions52 - | FormatOptions53 - | FormatOptions54 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions42] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown9(PriceBreakdown): - pass - - -class PricingOptions11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown9 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown10(PriceBreakdown): - pass - - -class PricingOptions12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown10 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown11(PriceBreakdown): - pass - - -class PricingOptions13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown11 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown12(PriceBreakdown): - pass - - -class PricingOptions14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown12 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown13(PriceBreakdown): - pass - - -class PricingOptions15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters4, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown13 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown14(PriceBreakdown): - pass - - -class PricingOptions16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters1, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown14 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown15(PriceBreakdown): - pass - - -class PricingOptions17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown15 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown16(PriceBreakdown): - pass - - -class PricingOptions18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters6 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown16 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown17(PriceBreakdown): - pass - - -class PricingOptions19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters7, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown17 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions10( - RootModel[ - PricingOptions11 - | PricingOptions12 - | PricingOptions13 - | PricingOptions14 - | PricingOptions15 - | PricingOptions16 - | PricingOptions17 - | PricingOptions18 - | PricingOptions19 - ] -): - root: Annotated[ - PricingOptions11 - | PricingOptions12 - | PricingOptions13 - | PricingOptions14 - | PricingOptions15 - | PricingOptions16 - | PricingOptions17 - | PricingOptions18 - | PricingOptions19, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions6(Dimensions): - pass - - -class Dimensions8(Dimensions2): - pass - - -class Dimensions9(Dimensions3): - pass - - -class Dimensions10(Dimensions4): - pass - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo7 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride7 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor7 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo8 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride8 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor8, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[Dimensions6 | Dimensions7 | Dimensions8 | Dimensions9 | Dimensions10 | Dimensions11] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics1, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability1 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue1] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point1], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo9 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride9 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement1(AdCPBaseModel): - vendors: Annotated[ - list[Vendor9] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo10 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride10 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor10, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement1 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy1 | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo11 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride11 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor11, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo12 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride12 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor12, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric1] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown1 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class IncludedSignal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption12] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization1(MetricOptimization): - pass - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction16(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo13 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride13 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor13, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric3], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status25, - Field( - description="Overall measurement readiness level for this product given the buyer's event setup. 'insufficient' means the product cannot optimize effectively with the current setup.", - title='Assessment Status', - ), - ] - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue2] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image1 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction18(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage1 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage1] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Products1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty8], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] = None - format_options: Annotated[ - list[FormatOptions28], - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] - placements: Annotated[ - list[Placement1] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: Annotated[ - DeliveryType, Field(description='Type of inventory delivery', title='Delivery Type') - ] - exclusivity: Annotated[ - Exclusivity | None, - Field( - description="Whether this product offers exclusive access to its inventory. Defaults to 'none' when absent. Most relevant for guaranteed products tied to specific collections or placements.", - title='Exclusivity', - ), - ] = None - pricing_options: Annotated[ - list[PricingOptions10], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast1 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement1 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement1 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms1 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard1] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy1 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction1] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities1, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy1 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal1] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption1] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules1 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization1 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization1 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness1 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking1 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch1 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard1 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed1 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment1] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch1 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Dimensions12(Dimensions): - pass - - -class Dimensions14(Dimensions2): - pass - - -class Dimensions15(Dimensions3): - pass - - -class Dimensions16(Dimensions4): - pass - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo14 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride14 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor14 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo15 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride15 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor15, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions12 | Dimensions13 | Dimensions14 | Dimensions15 | Dimensions16 | Dimensions17 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics2, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability2 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue2] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point2], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Allocation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, Field(description='ID of the product (must reference a product in the products array)') - ] - allocation_percentage: Annotated[ - float, - Field( - description='Percentage of total budget allocated to this product (0-100)', - ge=0.0, - le=100.0, - ), - ] - pricing_option_id: Annotated[ - str | None, - Field(description="Recommended pricing option ID from the product's pricing_options array"), - ] = None - rationale: Annotated[ - str | None, - Field(description='Explanation of why this product and allocation are recommended'), - ] = None - sequence: Annotated[ - int | None, - Field(description='Optional ordering hint for multi-line-item plans (1-based)', ge=1), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Categorical tags for this allocation (e.g., 'desktop', 'german', 'mobile') - useful for grouping/filtering allocations by dimension" - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight start date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight end date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Recommended time windows for this allocation in spot-plan proposals.', - min_length=1, - ), - ] = None - forecast: Annotated[ - Forecast2 | None, - Field( - description='Forecasted delivery metrics for this allocation', title='Delivery Forecast' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Dimensions18(Dimensions): - pass - - -class Dimensions20(Dimensions2): - pass - - -class Dimensions21(Dimensions3): - pass - - -class Dimensions22(Dimensions4): - pass - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo16 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride16 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor16 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo17 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride17 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor17, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions18 | Dimensions19 | Dimensions20 | Dimensions21 | Dimensions22 | Dimensions23 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics3, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability3 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue3] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point3], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Proposal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - proposal_id: Annotated[ - str, - Field( - description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', - max_length=255, - ), - ] - name: Annotated[ - str, Field(description='Human-readable name for this media plan proposal', max_length=500) - ] - description: Annotated[ - str | None, - Field( - description='Explanation of the proposal strategy and what it achieves', max_length=2000 - ), - ] = None - allocations: Annotated[ - list[Allocation], - Field( - description='Budget allocations across products. Allocation percentages MUST sum to 100. Publishers are responsible for ensuring the sum equals 100; buyers SHOULD validate this before execution.', - min_length=1, - ), - ] - proposal_status: Annotated[ - ProposalStatus | None, - Field( - description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy.", - title='Proposal Status', - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' - ), - ] = None - insertion_order: Annotated[ - InsertionOrder | None, - Field( - description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.', - title='Insertion Order', - ), - ] = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') - ] = None - brief_alignment: Annotated[ - str | None, - Field( - description='Explanation of how this proposal aligns with the campaign brief', - max_length=2000, - ), - ] = None - forecast: Annotated[ - Forecast3 | None, - Field( - description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.', - title='Delivery Forecast', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class GetProductsResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - products: Annotated[ - list[Products | Products1] | None, Field(description='Array of matching products') - ] = None - extensions: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, - Field( - description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `@` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' - ), - ] = None - proposals: Annotated[ - list[Proposal] | None, - Field( - description='Optional array of proposed media plans with budget allocations across products. Publishers include proposals when they can provide strategic guidance based on the brief. Proposals are actionable - buyers can refine them via follow-up get_products calls within the same session, or execute them directly via create_media_buy.' - ), - ] = None - errors: Annotated[ - list[Error] | None, - Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), - ] = None - property_list_applied: Annotated[ - bool | None, - Field( - description='[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent.' - ), - ] = None - catalog_applied: Annotated[ - bool | None, - Field( - description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' - ), - ] = None - refinement_applied: Annotated[ - list[RefinementApplied] | None, - Field( - description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - filter_diagnostics: Annotated[ - FilterDiagnostics | None, - Field( - description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", - examples=[ - { - 'semantics': 'only', - 'total_candidates': 47, - 'excluded_by': { - 'required_metrics': {'count': 31, 'values': ['completed_views']}, - 'required_geo_targeting': {'count': 9}, - 'pricing_currencies': {'count': 3, 'values': ['USD']}, - 'budget_range': {'count': 7}, - }, - } - ], - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under (agent, buying_mode, filters, property_list, catalog, account_id). When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay for the (agent, filters, mode) tuple. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 sellers correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = CacheScope.public - unchanged: Annotated[ - Literal[True] | None, - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_request.py b/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_request.py deleted file mode 100644 index 44fd80fc..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_request.py +++ /dev/null @@ -1,254 +0,0 @@ -# generated by datamodel-codegen: -# filename: list_creative_formats_request.json -# timestamp: 2026-06-04T19:45:55+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, ConfigDict, Field - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - url = 'url' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - - -class WcagLevel(StrEnum): - A = 'A' - AA = 'AA' - AAA = 'AAA' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class DisclosurePersistenceEnum(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class OutputFormatId(FormatId): - pass - - -class InputFormatId(FormatId): - pass - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class ListCreativeFormatsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Return only these specific format IDs (e.g., from get_products response)', - min_length=1, - ), - ] = None - asset_types: Annotated[ - list[AssetType] | None, - Field( - description="Filter to formats that include these asset types. For third-party tags, search for 'html' or 'javascript'. For published-post reference formats, search for 'published_post'. E.g., ['image', 'text'] returns formats with images and text, ['javascript'] returns formats accepting JavaScript tags.", - min_length=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum width in pixels (inclusive). Returns formats where ANY render has width <= this value. For multi-render formats, matches if at least one render fits.' - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum height in pixels (inclusive). Returns formats where ANY render has height <= this value. For multi-render formats, matches if at least one render fits.' - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum width in pixels (inclusive). Returns formats where ANY render has width >= this value.' - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum height in pixels (inclusive). Returns formats where ANY render has height >= this value.' - ), - ] = None - is_responsive: Annotated[ - bool | None, - Field( - description='Filter for responsive formats that adapt to container size. When true, returns formats without fixed dimensions.' - ), - ] = None - name_search: Annotated[ - str | None, Field(description='Search for formats by name (case-insensitive partial match)') - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description='Filter to formats supported by the named publisher. Agents resolve via the three-tier order documented in `docs/creative/canonical-formats.mdx#format-discovery` (publisher\'s hosted adagents.json → AAO community mirror → agent-derived from own products\' format_options). All fetches in the chain MUST follow the same transport contract as `format_schema` (https-only, SSRF guards, ≤5s timeout, 1 MiB cap, no redirects — see `static/schemas/source/core/product-format-declaration.json#format_schema`). Response carries `source: "publisher" | "aao_mirror" | "agent_derived"` so buyers know which tier produced the list. The pattern below is a syntactic floor — NOT an SSRF guard; agents MUST resolve the hostname and reject private/loopback/link-local/metadata IPs before fetching.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - property_id: Annotated[ - str | None, - Field( - description="Filter to formats supported on the named property within the publisher's catalog. Resolves to a property in the publisher's `adagents.json` `properties[]`; the agent returns only `formats[]` entries whose `applies_to_property_ids` includes this property (or entries with no scope, which apply to all properties). Typically used in combination with `publisher_domain`.", - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] = None - wcag_level: Annotated[ - WcagLevel | None, - Field( - description='Filter to formats that meet at least this WCAG conformance level (A < AA < AAA)', - title='WCAG Level', - ), - ] = None - disclosure_positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description="Filter to formats that support all of these disclosure positions. When a format has disclosure_capabilities, match against those positions. Otherwise fall back to supported_disclosure_positions. Use to find formats compatible with a brief's compliance requirements.", - min_length=1, - ), - ] = None - disclosure_persistence: Annotated[ - list[DisclosurePersistenceEnum] | None, - Field( - description='Filter to formats where each requested persistence mode is supported by at least one position in disclosure_capabilities. Different positions may satisfy different modes. Use to find formats compatible with jurisdiction-specific persistence requirements (e.g., continuous for EU AI Act).', - min_length=1, - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId] | None, - Field( - description="Filter to formats whose output_format_ids includes any of these format IDs. Returns formats that can produce these outputs — inspect each result's input_format_ids to see what inputs they accept.", - min_length=1, - ), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - description="Filter to formats whose input_format_ids includes any of these format IDs. Returns formats that accept these creatives as input — inspect each result's output_format_ids to see what they can produce.", - min_length=1, - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_response.py b/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_response.py deleted file mode 100644 index dbc88879..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/list_creative_formats_response.py +++ /dev/null @@ -1,6324 +0,0 @@ -# generated by datamodel-codegen: -# filename: list_creative_formats_response.json -# timestamp: 2026-06-18T11:30:54+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class AcceptsParameter(StrEnum): - dimensions = 'dimensions' - duration = 'duration' - - -class Responsive(AdCPBaseModel): - width: bool - height: bool - - -class Format1(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - avif = 'avif' - tiff = 'tiff' - pdf = 'pdf' - eps = 'eps' - - -class Bleed(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - uniform: Annotated[float, Field(description='Same bleed on all four sides', ge=0.0)] - - -class Bleed1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - top: Annotated[float, Field(ge=0.0)] - right: Annotated[float, Field(ge=0.0)] - bottom: Annotated[float, Field(ge=0.0)] - left: Annotated[float, Field(ge=0.0)] - - -class ColorSpace(StrEnum): - rgb = 'rgb' - cmyk = 'cmyk' - grayscale = 'grayscale' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - avi = 'avi' - mkv = 'mkv' - - -class Codec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class FrameRate(RootModel[float]): - root: Annotated[float, Field(ge=1.0)] - - -class AudioCodec(StrEnum): - aac = 'aac' - pcm = 'pcm' - ac3 = 'ac3' - eac3 = 'eac3' - mp3 = 'mp3' - opus = 'opus' - vorbis = 'vorbis' - flac = 'flac' - - -class AudioSampleRate(RootModel[int]): - root: Annotated[int, Field(ge=1)] - - -class Format2(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - ogg = 'ogg' - flac = 'flac' - - -class SampleRate(AudioSampleRate): - pass - - -class Channel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class Requirements2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_duration_ms: Annotated[ - int | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - int | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - formats: Annotated[list[Format2] | None, Field(description='Accepted audio file formats')] = ( - None - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - sample_rates: Annotated[ - list[SampleRate] | None, - Field(description='Accepted sample rates in Hz (e.g., [44100, 48000])'), - ] = None - channels: Annotated[ - list[Channel] | None, Field(description='Accepted audio channel configurations') - ] = None - min_bitrate_kbps: Annotated[ - int | None, Field(description='Minimum audio bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - int | None, Field(description='Maximum audio bitrate in kilobits per second', ge=1) - ] = None - - -class Requirements3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_length: Annotated[int | None, Field(description='Minimum character length', ge=0)] = None - max_length: Annotated[int | None, Field(description='Maximum character length', ge=1)] = None - min_lines: Annotated[int | None, Field(description='Minimum number of lines', ge=1)] = None - max_lines: Annotated[int | None, Field(description='Maximum number of lines', ge=1)] = None - character_pattern: Annotated[ - str | None, - Field( - description="Regex pattern defining allowed characters (e.g., '^[a-zA-Z0-9 .,!?-]+$')" - ), - ] = None - prohibited_terms: Annotated[ - list[str] | None, Field(description='List of prohibited words or phrases') - ] = None - allowed_values: Annotated[ - list[str] | None, - Field( - description='Closed set of permitted string values for this text slot. When present, a conformant implementation MUST reject any submitted content not in this list with `CREATIVE_VALUE_NOT_ALLOWED` (echoing the offending field path in `error.field` and the allowed list in `error.details.allowed_values`). Matching is case-sensitive; producers MUST supply exact casing. If the submitted value contains unresolved template tokens (e.g., {{product_name}}), validation against allowed_values MUST be deferred until interpolation is complete. All declared constraints (allowed_values, character_pattern, max_length, etc.) are conjunctive — submitted content must satisfy every applicable constraint simultaneously.', - min_length=1, - ), - ] = None - - -class Requirements4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_length: Annotated[int | None, Field(description='Maximum character length', ge=1)] = None - - -class Sandbox(StrEnum): - none = 'none' - iframe = 'iframe' - safeframe = 'safeframe' - fencedframe = 'fencedframe' - - -class Requirements5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes for the HTML asset', ge=1) - ] = None - sandbox: Annotated[ - Sandbox | None, - Field( - description="Sandbox environment the HTML must be compatible with. 'none' = direct DOM access, 'iframe' = standard iframe isolation, 'safeframe' = IAB SafeFrame container, 'fencedframe' = Privacy Sandbox fenced frame" - ), - ] = Sandbox.none - external_resources_allowed: Annotated[ - bool | None, - Field( - description='Whether the HTML creative can load external resources (scripts, images, fonts, etc.). When false, all resources must be inlined or bundled.' - ), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the HTML creative may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None - - -class Requirements6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - - -class ModuleType(StrEnum): - script = 'script' - module = 'module' - iife = 'iife' - - -class Requirements7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - max_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for the JavaScript asset', ge=1), - ] = None - module_type: Annotated[ - ModuleType | None, - Field( - description="Required JavaScript module format. 'script' = classic script, 'module' = ES modules, 'iife' = immediately invoked function expression" - ), - ] = None - strict_mode_required: Annotated[ - bool | None, Field(description='Whether the JavaScript must use strict mode') - ] = None - external_resources_allowed: Annotated[ - bool | None, - Field(description='Whether the JavaScript can load external resources dynamically'), - ] = None - allowed_external_domains: Annotated[ - list[str] | None, - Field( - description='List of domains the JavaScript may reference for external resources. Only applicable when external_resources_allowed is true.' - ), - ] = None - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class Requirements8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version')] = None - - -class Requirements9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - daast_version: Annotated[ - Literal['1.0'] | None, - Field(description='Required DAAST version. DAAST 1.0 is the current IAB standard.'), - ] = None - - -class Role(StrEnum): - clickthrough = 'clickthrough' - landing_page = 'landing_page' - impression_tracker = 'impression_tracker' - click_tracker = 'click_tracker' - viewability_tracker = 'viewability_tracker' - third_party_tracker = 'third_party_tracker' - - -class Protocol(StrEnum): - https = 'https' - http = 'http' - - -class Requirements10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: Annotated[ - Role | None, - Field( - description="Purpose this URL slot serves in the format — distinct from `url_type` on the manifest-side asset (which declares the receiver's invocation mechanism). A slot can be `click_tracker` (purpose) and accept a `tracker_pixel` (mechanism) URL, or `clickthrough` (purpose) and accept a `clickthrough` (mechanism) URL. Complements `asset_role` (human-readable label) by providing a machine-readable enum and serves as the receiver's fallback signal when a manifest URL asset omits `url_type`." - ), - ] = None - protocols: Annotated[ - list[Protocol] | None, - Field(description='Allowed URL protocols. HTTPS is recommended for all ad URLs.'), - ] = None - allowed_domains: Annotated[ - list[str] | None, Field(description='List of allowed domains for the URL') - ] = None - max_length: Annotated[ - int | None, Field(description='Maximum URL length in characters', ge=1) - ] = None - macro_support: Annotated[ - bool | None, - Field(description='Whether the URL supports macro substitution (e.g., ${CACHEBUSTER})'), - ] = None - - -class Method(StrEnum): - GET = 'GET' - POST = 'POST' - - -class Requirements11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - methods: Annotated[list[Method] | None, Field(description='Allowed HTTP methods')] = None - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - url = 'url' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - - -class Bleed2(Bleed): - pass - - -class Bleed3(Bleed1): - pass - - -class AssetRequirements2(Requirements2): - pass - - -class AssetRequirements3(Requirements3): - pass - - -class AssetRequirements4(Requirements4): - pass - - -class AssetRequirements5(Requirements5): - pass - - -class AssetRequirements6(Requirements6): - pass - - -class AssetRequirements7(Requirements7): - pass - - -class AssetRequirements8(Requirements8): - pass - - -class AssetRequirements9(Requirements9): - pass - - -class AssetRequirements10(Requirements10): - pass - - -class AssetRequirements11(Requirements11): - pass - - -class SelectionMode(StrEnum): - sequential = 'sequential' - optimize = 'optimize' - - -class Bleed4(Bleed): - pass - - -class Bleed5(Bleed1): - pass - - -class Requirements15(Requirements2): - pass - - -class Requirements16(Requirements3): - pass - - -class Requirements17(Requirements4): - pass - - -class Requirements18(Requirements5): - pass - - -class Requirements19(Requirements6): - pass - - -class Requirements20(Requirements7): - pass - - -class Requirements21(Requirements8): - pass - - -class Requirements22(Requirements9): - pass - - -class Requirements23(Requirements10): - pass - - -class Requirements24(Requirements11): - pass - - -class SupportedMacros(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class InputFormatId(FormatId): - pass - - -class OutputFormatId(FormatId): - pass - - -class FormatCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description='Creative format defining the card layout (typically format_card_standard)', - title='Format Reference (Structured Object)', - ), - ] - manifest: Annotated[ - dict[str, Any], - Field(description='Asset manifest for rendering the card, structure defined by the format'), - ] - - -class WcagLevel(StrEnum): - A = 'A' - AA = 'AA' - AAA = 'AAA' - - -class Accessibility(AdCPBaseModel): - wcag_level: Annotated[ - WcagLevel, - Field( - description='WCAG conformance level that this format achieves. For format-rendered creatives, the format guarantees this level. For opaque creatives, the format requires assets that self-certify to this level.', - title='WCAG Level', - ), - ] - requires_accessible_assets: Annotated[ - bool | None, - Field( - description='When true, all assets with x-accessibility fields must include those fields. For inspectable assets (image, video, audio), this means providing accessibility metadata like alt_text or captions. For opaque assets (HTML, JavaScript), this means providing self-declared accessibility properties.' - ), - ] = None - - -class PersistenceEnum(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class FormatCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description='Creative format defining the detailed card layout (typically format_card_detailed)', - title='Format Reference (Structured Object)', - ), - ] - manifest: Annotated[ - dict[str, Any], - Field( - description='Asset manifest for rendering the detailed card, structure defined by the format' - ), - ] - - -class ReportedMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption6(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption7(PricingOption1, PricingOption6): - pass - - -class PricingOption8(PricingOption2, PricingOption6): - pass - - -class PricingOption9(PricingOption3, PricingOption6): - pass - - -class PricingOption10(PricingOption4, PricingOption6): - pass - - -class PricingOption11(PricingOption5, PricingOption6): - pass - - -class PricingOption( - RootModel[PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11] -): - root: Annotated[ - PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Kind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class SlotsOverrideItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Asset group identifier from `asset-group-vocabulary.json` (e.g., `generation_prompt`, `creative_brief`, `image_main`, `video_main`).' - ), - ] - asset_type: Annotated[ - str, - Field( - description='Asset type — `image`, `video`, `audio`, `text`, `html`, `javascript`, `url`, `zip`, `brief`, `catalog`, `published_post`, or another canonical slot asset type.' - ), - ] - required: Annotated[ - bool | None, Field(description='Whether the slot is required in the projected declaration.') - ] = False - max_chars: Annotated[ - int | None, Field(description='Max character count for text slots.', ge=1) - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="When false, slot is for moderation/review only and is NOT consumed by the seller's renderer (e.g., a brand-safety brief that informs review but doesn't appear in the rendered ad)." - ), - ] = True - - -class Canonical(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description='The v2 canonical-format-kind this v1 format projects to (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `sponsored_placement`, `responsive_creative`, `agent_placement`, or `custom`).', - title='Canonical Format Kind', - ), - ] - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from on the projected v2 declaration. Default (when omitted) is `buyer_uploaded` — the canonical's default. Set explicitly when the v1 named format doesn't follow that default. Required for generative entries (`agent_synthesized` or `seller_pre_rendered_from_brief`) because their asset shape doesn't carry image/video/audio bytes, and for published-post reference entries (`publisher_owned_reference`) because their asset shape carries a post reference rather than uploaded bytes. Projection without this hint produces a lossy v2 declaration that claims buyer-uploaded bytes." - ), - ] = None - slots_override: Annotated[ - list[SlotsOverrideItem] | None, - Field( - description="When the v1 named format's slot shape differs from the canonical's default slots, this carries the override that the projected v2 declaration's `params.slots[]` should use. REPLACES (does not merge with) the canonical's default slots — projection-time semantics. The slot vocabulary follows `asset-group-vocabulary.json`. Asset IDs in the v1 format's `assets[*]` MUST resolve (directly or via the vocabulary's aliases) to the `asset_group_id` values declared here.", - min_length=1, - ), - ] = None - - -class AppliesToChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class V1FormatRefItem(FormatId): - pass - - -class FormatSchema(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class PlatformExtension(FormatSchema): - pass - - -class AssetType1(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(RootModel[str]): - root: Annotated[ - str, - Field( - examples=[ - 'list_creatives', - 'sync_creatives', - 'create_media_buy', - 'get_media_buy_delivery', - 'get_creative_delivery', - ], - min_length=1, - ), - ] - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status1(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class RequiredConnection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status1 | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[int, Field(ge=1)] - height: Annotated[int, Field(ge=1)] - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RequiredConnection1(RequiredConnection): - pass - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class RequiredConnection2(RequiredConnection): - pass - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class RequiredConnection3(RequiredConnection): - pass - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class RequiredConnection4(RequiredConnection): - pass - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(RootModel[int]): - root: Annotated[int, Field(ge=0)] - - -class AudioCodec3(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container3(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(AudioSampleRate): - pass - - -class CompanionBannerHeight(AudioSampleRate): - pass - - -class RequiredConnection5(RequiredConnection): - pass - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class RequiredConnection6(RequiredConnection): - pass - - -class AudioCodec4(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class RequiredConnection7(RequiredConnection): - pass - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class RequiredConnection8(RequiredConnection): - pass - - -class SupportedCatalogType(StrEnum): - product = 'product' - store = 'store' - offering = 'offering' - hotel = 'hotel' - flight = 'flight' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - job = 'job' - inventory = 'inventory' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class RequiredConnection9(RequiredConnection): - pass - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat1(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource4(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class RequiredConnection10(RequiredConnection): - pass - - -class RequiredConnection11(RequiredConnection): - pass - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class CanonicalParameters12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class Source1(StrEnum): - publisher = 'publisher' - aao_mirror = 'aao_mirror' - agent_derived = 'agent_derived' - - -class Capability(StrEnum): - validation = 'validation' - assembly = 'assembly' - generation = 'generation' - preview = 'preview' - delivery = 'delivery' - - -class CreativeAgent(AdCPBaseModel): - agent_url: Annotated[ - AnyUrl, - Field( - description="Base URL for the creative agent (e.g., 'https://reference.example.com', 'https://dco.example.com')." - ), - ] - agent_name: Annotated[ - str | None, Field(description='Human-readable name for the creative agent') - ] = None - capabilities: Annotated[ - list[Capability] | None, Field(description='Capabilities this creative agent provides') - ] = None - - -class Issue1(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue1] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class ScalarBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['scalar'] = 'scalar' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - catalog_field: Annotated[ - str, - Field( - description="Dot-notation path to the field on the catalog item (e.g., 'name', 'price.amount', 'location.city')." - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AssetPoolBinding(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['asset_pool'] = 'asset_pool' - asset_id: Annotated[ - str, - Field( - description="The asset_id from the format's assets array. Identifies which individual template slot this binding applies to." - ), - ] - asset_group_id: Annotated[ - str, - Field( - description="The asset_group_id on the catalog item's assets array to pull from (e.g., 'images_landscape', 'images_vertical', 'logo')." - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class DimensionUnit(StrEnum): - px = 'px' - dp = 'dp' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class Visual(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL to a theme-neutral overlay graphic (SVG or PNG). Use when a single file works for all backgrounds, e.g. an SVG using CSS custom properties or currentColor.' - ), - ] = None - light: Annotated[ - AnyUrl | None, - Field( - description='URL to the overlay graphic for use on light/bright backgrounds (SVG or PNG)' - ), - ] = None - dark: Annotated[ - AnyUrl | None, - Field(description='URL to the overlay graphic for use on dark backgrounds (SVG or PNG)'), - ] = None - - -class Unit(StrEnum): - px = 'px' - fraction = 'fraction' - inches = 'inches' - cm = 'cm' - mm = 'mm' - pt = 'pt' - - -class Bounds(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - x: Annotated[float, Field(description="Horizontal offset from the asset's left edge")] - y: Annotated[float, Field(description="Vertical offset from the asset's top edge")] - width: Annotated[float, Field(description='Width of the overlay', ge=0.0)] - height: Annotated[float, Field(description='Height of the overlay', ge=0.0)] - unit: Annotated[ - Unit, - Field( - description="'px' = absolute pixels from asset top-left. 'fraction' = proportional to asset dimensions (0.0 = edge, 1.0 = opposite edge). 'inches', 'cm', 'mm', 'pt' (1/72 inch) = physical units for print overlays, measured from asset top-left." - ), - ] - - -class Overlay(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable explanation of what this overlay is and how buyers should account for it' - ), - ] = None - visual: Annotated[ - Visual | None, - Field( - description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' - ), - ] = None - bounds: Annotated[ - Bounds, - Field( - description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." - ), - ] - - -class BaseGroupAsset(AdCPBaseModel): - asset_id: Annotated[str, Field(description='Identifier for this asset within the group')] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose. For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description='Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Same semantics as on baseIndividualAsset — lets buyers and migration tools resolve v1 author-invented slot names to canonical names.' - ), - ] = None - required: Annotated[ - bool, - Field(description='Whether this asset is required within each repetition of the group'), - ] - overlays: Annotated[ - list[Overlay] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., carousel navigation arrows, slide indicators). Creative agents should avoid placing critical content within overlay bounds." - ), - ] = None - - -class Bounds1(Bounds): - pass - - -class Overlay1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[ - str, - Field( - description="Identifier for this overlay (e.g., 'play_pause', 'volume', 'publisher_logo', 'carousel_prev', 'carousel_next')" - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable explanation of what this overlay is and how buyers should account for it' - ), - ] = None - visual: Annotated[ - Visual | None, - Field( - description='Optional visual reference for this overlay element. Useful for creative agents compositing previews and for buyers understanding what will appear over their content. Must include at least one of: url, light, or dark.' - ), - ] = None - bounds: Annotated[ - Bounds1, - Field( - description="Position and size of the overlay relative to the asset's own top-left corner. See 'unit' for coordinate interpretation." - ), - ] - - -class BaseIndividualAsset(AdCPBaseModel): - item_type: Annotated[ - Literal['individual'], - Field(description='Discriminator indicating this is an individual asset'), - ] = 'individual' - asset_id: Annotated[ - str, - Field( - description='Unique identifier for this asset. Creative manifests MUST use this exact value as the key in the assets object.' - ), - ] - asset_role: Annotated[ - str | None, - Field( - description="Descriptive label for this asset's purpose (e.g., 'hero_image', 'logo', 'third_party_tracking'). For documentation and UI display only — manifests key assets by asset_id, not asset_role." - ), - ] = None - required: Annotated[ - bool, - Field( - description='Whether this asset is required (true) or optional (false). Required assets must be provided for a valid creative. Optional assets enhance the creative but are not mandatory.' - ), - ] - overlays: Annotated[ - list[Overlay1] | None, - Field( - description="Publisher-controlled elements rendered on top of buyer content at this asset's position (e.g., video player controls, publisher logos). Creative agents should avoid placing critical content (CTAs, logos, key copy) within overlay bounds." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Optional canonical asset_group_id this slot fills, drawn from /schemas/core/asset-group-vocabulary.json. Lets buyers and migration tools resolve v1 author-invented slot names (e.g., `click_url`) to canonical names (e.g., `landing_page_url`). Validators MAY soft-warn when a v1 slot's asset_id is a known alias but no asset_group_id is declared." - ), - ] = None - - -class Dimensions(AdCPBaseModel): - width: Annotated[ - float | None, - Field(description='Fixed width. Interpretation depends on unit (default: pixels).', gt=0.0), - ] = None - height: Annotated[ - float | None, - Field( - description='Fixed height. Interpretation depends on unit (default: pixels).', gt=0.0 - ), - ] = None - min_width: Annotated[ - float | None, Field(description='Minimum width for responsive renders', gt=0.0) - ] = None - min_height: Annotated[ - float | None, Field(description='Minimum height for responsive renders', gt=0.0) - ] = None - max_width: Annotated[ - float | None, Field(description='Maximum width for responsive renders', gt=0.0) - ] = None - max_height: Annotated[ - float | None, Field(description='Maximum height for responsive renders', gt=0.0) - ] = None - unit: DimensionUnit | None = None - responsive: Annotated[ - Responsive | None, Field(description='Indicates which dimensions are responsive/fluid') - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Fixed aspect ratio constraint (e.g., '16:9', '4:3', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - - -class Renders(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - bool | None, - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] = None - dimensions: Annotated[ - Dimensions, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] - - -class Dimensions1(Dimensions): - pass - - -class Renders1(AdCPBaseModel): - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece (e.g., 'primary', 'companion', 'mobile_variant')" - ), - ] - parameters_from_format_id: Annotated[ - Literal[True], - Field( - description='When true, parameters for this render (dimensions and/or duration) are specified in the format_id. Used for template formats that accept parameters. Mutually exclusive with specifying dimensions object explicitly.' - ), - ] - dimensions: Annotated[ - Dimensions1 | None, - Field( - description='Dimensions for this rendered piece. Defaults to pixels when unit is absent.' - ), - ] = None - - -class Requirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format1] | None, Field(description='Accepted image file formats')] = ( - None - ) - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed | Bleed1 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class Assets(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['image'] = 'image' - requirements: Annotated[ - Requirements | None, - Field( - description='Requirements for image creative assets. These define the technical constraints for image files.', - title='Image Asset Requirements', - ), - ] = None - - -class Requirements1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[int | None, Field(description='Minimum width in pixels', ge=1)] = None - max_width: Annotated[int | None, Field(description='Maximum width in pixels', ge=1)] = None - min_height: Annotated[int | None, Field(description='Minimum height in pixels', ge=1)] = None - max_height: Annotated[int | None, Field(description='Maximum height in pixels', ge=1)] = None - aspect_ratio: Annotated[ - str | None, - Field(description="Required aspect ratio (e.g., '16:9', '9:16')", pattern='^\\d+:\\d+$'), - ] = None - min_duration_ms: Annotated[ - int | None, Field(description='Minimum duration in milliseconds', ge=1) - ] = None - max_duration_ms: Annotated[ - int | None, Field(description='Maximum duration in milliseconds', ge=1) - ] = None - containers: Annotated[ - list[Container] | None, Field(description='Accepted video container formats') - ] = None - codecs: Annotated[list[Codec] | None, Field(description='Accepted video codecs')] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - min_bitrate_kbps: Annotated[ - int | None, Field(description='Minimum video bitrate in kilobits per second', ge=1) - ] = None - max_bitrate_kbps: Annotated[ - int | None, Field(description='Maximum video bitrate in kilobits per second', ge=1) - ] = None - frame_rates: Annotated[ - list[FrameRate] | None, - Field(description='Accepted frame rates in frames per second (e.g., [24, 30, 60])'), - ] = None - audio_required: Annotated[ - bool | None, Field(description='Whether the video must include an audio track') - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - gop_type: GOPType | None = None - min_gop_interval_seconds: Annotated[ - float | None, Field(description='Minimum keyframe interval in seconds', ge=0.0) - ] = None - max_gop_interval_seconds: Annotated[ - float | None, - Field( - description='Maximum keyframe interval in seconds. SSAI typically requires 1-2 second intervals.', - ge=0.0, - ), - ] = None - moov_atom_position: MoovAtomPosition | None = None - audio_codecs: Annotated[ - list[AudioCodec] | None, - Field(description="Accepted audio codecs (e.g., ['aac', 'pcm', 'ac3'])"), - ] = None - audio_sample_rates: Annotated[ - list[AudioSampleRate] | None, - Field(description='Accepted audio sample rates in Hz (e.g., [44100, 48000])'), - ] = None - audio_channels: Annotated[ - list[AudioChannelLayout] | None, Field(description='Accepted audio channel configurations') - ] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Target integrated loudness in LUFS (e.g., -24 for broadcast, -16 for streaming)' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, - Field( - description='Acceptable deviation from loudness_lufs target in dB (e.g., 2 means -22 to -26 LUFS for a -24 target)', - ge=0.0, - ), - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true peak level in dBFS (e.g., -2 for broadcast)') - ] = None - - -class Assets1(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['video'] = 'video' - requirements: Annotated[ - Requirements1 | None, - Field( - description='Requirements for video creative assets. These define the technical constraints for video files.', - title='Video Asset Requirements', - ), - ] = None - - -class Assets2(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['audio'] = 'audio' - requirements: Annotated[ - Requirements2 | None, - Field( - description='Requirements for audio creative assets.', title='Audio Asset Requirements' - ), - ] = None - - -class Assets3(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['text'] = 'text' - requirements: Annotated[ - Requirements3 | None, - Field( - description='Requirements for text creative assets such as headlines, body copy, and CTAs.', - title='Text Asset Requirements', - ), - ] = None - - -class Assets4(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['markdown'] = 'markdown' - requirements: Annotated[ - Requirements4 | None, - Field( - description='Requirements for markdown creative assets.', - title='Markdown Asset Requirements', - ), - ] = None - - -class Assets5(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['html'] = 'html' - requirements: Annotated[ - Requirements5 | None, - Field( - description='Requirements for HTML creative assets. These define the execution environment constraints that the HTML must be compatible with.', - title='HTML Asset Requirements', - ), - ] = None - - -class Assets6(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['css'] = 'css' - requirements: Annotated[ - Requirements6 | None, - Field(description='Requirements for CSS creative assets.', title='CSS Asset Requirements'), - ] = None - - -class Assets7(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['javascript'] = 'javascript' - requirements: Annotated[ - Requirements7 | None, - Field( - description='Requirements for JavaScript creative assets. These define the execution environment constraints that the JavaScript must be compatible with.', - title='JavaScript Asset Requirements', - ), - ] = None - - -class Assets8(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['zip'] = 'zip' - - -class Assets9(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['vast'] = 'vast' - requirements: Annotated[ - Requirements8 | None, - Field( - description='Requirements for VAST (Video Ad Serving Template) creative assets.', - title='VAST Asset Requirements', - ), - ] = None - - -class Assets10(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['daast'] = 'daast' - requirements: Annotated[ - Requirements9 | None, - Field( - description='Requirements for DAAST (Digital Audio Ad Serving Template) creative assets.', - title='DAAST Asset Requirements', - ), - ] = None - - -class Assets11(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['url'] = 'url' - requirements: Annotated[ - Requirements10 | None, - Field( - description='Requirements for URL assets such as click-through URLs, tracking pixels, and landing pages.', - title='URL Asset Requirements', - ), - ] = None - - -class Assets12(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['webhook'] = 'webhook' - requirements: Annotated[ - Requirements11 | None, - Field( - description='Requirements for webhook creative assets.', - title='Webhook Asset Requirements', - ), - ] = None - - -class Assets13(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['brief'] = 'brief' - - -class AssetRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format1] | None, Field(description='Accepted image file formats')] = ( - None - ) - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed2 | Bleed3 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class AssetRequirements1(Requirements1): - pass - - -class OfferingAssetConstraint(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description="The asset group this constraint applies to. Values are format-defined vocabulary — each format chooses its own group IDs (e.g., 'headlines', 'images', 'videos'). Buyers discover them via list_creative_formats." - ), - ] - asset_type: Annotated[ - AssetType, - Field(description='The expected content type for this group.', title='Asset Content Type'), - ] - required: Annotated[ - bool | None, - Field( - description='Whether this asset group must be present in each offering. Defaults to true.' - ), - ] = True - min_count: Annotated[ - int | None, Field(description='Minimum number of items required in this group.', ge=1) - ] = None - max_count: Annotated[ - int | None, Field(description='Maximum number of items allowed in this group.', ge=1) - ] = None - asset_requirements: Annotated[ - AssetRequirements - | AssetRequirements1 - | AssetRequirements2 - | AssetRequirements3 - | AssetRequirements4 - | AssetRequirements5 - | AssetRequirements6 - | AssetRequirements7 - | AssetRequirements8 - | AssetRequirements9 - | AssetRequirements10 - | AssetRequirements11 - | None, - Field( - description='Technical requirements for each item in this group (e.g., max_length for text, min_width/aspect_ratio for images). Applies uniformly to all items in the group.', - title='Asset Requirements', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PerItemBindings(RootModel[ScalarBinding | AssetPoolBinding]): - root: Annotated[ScalarBinding | AssetPoolBinding, Field(discriminator='kind')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FieldBindings1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['catalog_group'] = 'catalog_group' - format_group_id: Annotated[ - str, - Field(description="The asset_group_id of a repeatable_group in the format's assets array."), - ] - catalog_item: Annotated[ - Literal[True], - Field( - description="Each repetition of the format's repeatable_group maps to one item from the catalog." - ), - ] - per_item_bindings: Annotated[ - list[PerItemBindings] | None, - Field( - description='Scalar and asset pool bindings that apply within each repetition of the group. Nested catalog_group bindings are not permitted.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FieldBindings(RootModel[ScalarBinding | AssetPoolBinding | FieldBindings1]): - root: Annotated[ - ScalarBinding | AssetPoolBinding | FieldBindings1, - Field( - description="Maps a format template slot to a catalog item field or typed asset pool. The 'kind' field identifies the binding variant. All bindings are optional — agents can still infer mappings without them.", - discriminator='kind', - examples=[ - { - 'description': 'Scalar binding — hotel name to headline slot', - 'data': {'kind': 'scalar', 'asset_id': 'headline', 'catalog_field': 'name'}, - }, - { - 'description': 'Scalar binding — nested field (nightly rate)', - 'data': { - 'kind': 'scalar', - 'asset_id': 'price_badge', - 'catalog_field': 'price.amount', - }, - }, - { - 'description': 'Asset pool binding — hero image from landscape pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'hero_image', - 'asset_group_id': 'images_landscape', - }, - }, - { - 'description': 'Asset pool binding — Snap vertical background from vertical pool', - 'data': { - 'kind': 'asset_pool', - 'asset_id': 'snap_background', - 'asset_group_id': 'images_vertical', - }, - }, - { - 'description': 'Catalog group binding — carousel where each slide is one hotel', - 'data': { - 'kind': 'catalog_group', - 'format_group_id': 'slide', - 'catalog_item': True, - 'per_item_bindings': [ - {'kind': 'scalar', 'asset_id': 'title', 'catalog_field': 'name'}, - { - 'kind': 'scalar', - 'asset_id': 'price', - 'catalog_field': 'price.amount', - }, - { - 'kind': 'asset_pool', - 'asset_id': 'image', - 'asset_group_id': 'images_landscape', - }, - ], - }, - }, - ], - title='Catalog Field Binding', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Requirements12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_type: Annotated[ - CatalogType, - Field(description='The catalog type this requirement applies to', title='Catalog Type'), - ] - required: Annotated[ - bool | None, - Field( - description='Whether this catalog type must be present. When true, creatives using this format must reference a synced catalog of this type.' - ), - ] = True - min_items: Annotated[ - int | None, - Field( - description='Minimum number of items the catalog must contain for this format to render properly (e.g., a carousel might require at least 3 products)', - ge=1, - ), - ] = None - max_items: Annotated[ - int | None, - Field( - description='Maximum number of items the format can render. Items beyond this limit are ignored. Useful for fixed-slot layouts (e.g., a 3-product card) or feed-size constraints.', - ge=1, - ), - ] = None - required_fields: Annotated[ - list[str] | None, - Field( - description="Fields that must be present and non-empty on every item in the catalog. Field names are catalog-type-specific (e.g., 'title', 'price', 'image_url' for product catalogs; 'store_id', 'quantity' for inventory feeds).", - min_length=1, - ), - ] = None - feed_formats: Annotated[ - list[FeedFormat] | None, - Field( - description='Accepted feed formats for this catalog type. When specified, the synced catalog must use one of these formats. When omitted, any format is accepted.', - min_length=1, - ), - ] = None - offering_asset_constraints: Annotated[ - list[OfferingAssetConstraint] | None, - Field( - description="Per-item creative asset requirements. Declares what asset groups (headlines, images, videos) each catalog item must provide in its assets array, along with count bounds and per-asset technical constraints. Applicable to 'offering' and all vertical catalog types (hotel, flight, job, etc.) whose items carry typed assets.", - min_length=1, - ), - ] = None - field_bindings: Annotated[ - list[FieldBindings] | None, - Field( - description='Explicit mappings from format template slots to catalog item fields or typed asset pools. Optional — creative agents can infer mappings without them, but bindings make the relationship self-describing and enable validation. Covers scalar fields (asset_id → catalog_field), asset pools (asset_id → asset_group_id on the catalog item), and repeatable groups that iterate over catalog items.', - min_length=1, - ), - ] = None - - -class Assets14(BaseIndividualAsset): - item_type: Literal['individual'] = 'individual' - asset_type: Literal['catalog'] = 'catalog' - requirements: Annotated[ - Requirements12 | None, - Field( - description='Format-level declaration of what catalog feeds a creative needs. Formats that render product listings, store locators, or promotional content declare which catalog types must be synced and what fields each catalog must provide. Buyers use this to ensure the right catalogs are synced before submitting creatives.', - title='Catalog Requirements', - ), - ] = None - - -class Requirements13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min_width: Annotated[ - float | None, - Field( - description='Minimum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - max_width: Annotated[ - float | None, - Field( - description='Maximum width. Interpretation depends on unit (default: pixels). For exact dimensions, set min_width = max_width.', - gt=0.0, - ), - ] = None - min_height: Annotated[ - float | None, - Field( - description='Minimum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - max_height: Annotated[ - float | None, - Field( - description='Maximum height. Interpretation depends on unit (default: pixels). For exact dimensions, set min_height = max_height.', - gt=0.0, - ), - ] = None - unit: DimensionUnit | None = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Required aspect ratio (e.g., '16:9', '1:1', '1.91:1')", - pattern='^\\d+(\\.\\d+)?:\\d+(\\.\\d+)?$', - ), - ] = None - formats: Annotated[list[Format1] | None, Field(description='Accepted image file formats')] = ( - None - ) - min_dpi: Annotated[ - int | None, - Field( - description='Minimum resolution in dots per inch. Always in DPI regardless of the dimension unit. Standard print requires 300 DPI, newspaper 150 DPI.', - ge=1, - ), - ] = None - bleed: Annotated[ - Bleed4 | Bleed5 | None, - Field( - description='Required bleed area beyond the trim size. The submitted image must be larger than the declared dimensions: total width = trim width + left bleed + right bleed, total height = trim height + top bleed + bottom bleed. For uniform bleed: total = trim + (2 * uniform). Uses the same unit as the parent dimensions.' - ), - ] = None - color_space: Annotated[ - ColorSpace | None, Field(description='Required color space. Print typically requires CMYK.') - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes', ge=1) - ] = None - transparency_required: Annotated[ - bool | None, - Field( - description='Whether the image must support transparency (requires PNG, WebP, or GIF)' - ), - ] = None - animation_allowed: Annotated[ - bool | None, Field(description='Whether animated images (GIF, animated WebP) are accepted') - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum animation duration in milliseconds (if animation_allowed is true)', - ge=0, - ), - ] = None - max_weight_grams: Annotated[ - int | None, - Field( - description='Maximum weight in grams for the finished physical piece (print inserts, flyers). Affects postage calculations and production constraints. Only applicable to print channels.', - gt=0, - ), - ] = None - - -class Assets17(BaseGroupAsset): - asset_type: Literal['image'] = 'image' - requirements: Annotated[ - Requirements13 | None, - Field( - description='Requirements for image creative assets. These define the technical constraints for image files.', - title='Image Asset Requirements', - ), - ] = None - - -class Requirements14(Requirements1): - pass - - -class Assets18(BaseGroupAsset): - asset_type: Literal['video'] = 'video' - requirements: Annotated[ - Requirements14 | None, - Field( - description='Requirements for video creative assets. These define the technical constraints for video files.', - title='Video Asset Requirements', - ), - ] = None - - -class Assets19(BaseGroupAsset): - asset_type: Literal['audio'] = 'audio' - requirements: Annotated[ - Requirements15 | None, - Field( - description='Requirements for audio creative assets.', title='Audio Asset Requirements' - ), - ] = None - - -class Assets20(BaseGroupAsset): - asset_type: Literal['text'] = 'text' - requirements: Annotated[ - Requirements16 | None, - Field( - description='Requirements for text creative assets such as headlines, body copy, and CTAs.', - title='Text Asset Requirements', - ), - ] = None - - -class Assets21(BaseGroupAsset): - asset_type: Literal['markdown'] = 'markdown' - requirements: Annotated[ - Requirements17 | None, - Field( - description='Requirements for markdown creative assets.', - title='Markdown Asset Requirements', - ), - ] = None - - -class Assets22(BaseGroupAsset): - asset_type: Literal['html'] = 'html' - requirements: Annotated[ - Requirements18 | None, - Field( - description='Requirements for HTML creative assets. These define the execution environment constraints that the HTML must be compatible with.', - title='HTML Asset Requirements', - ), - ] = None - - -class Assets23(BaseGroupAsset): - asset_type: Literal['css'] = 'css' - requirements: Annotated[ - Requirements19 | None, - Field(description='Requirements for CSS creative assets.', title='CSS Asset Requirements'), - ] = None - - -class Assets24(BaseGroupAsset): - asset_type: Literal['javascript'] = 'javascript' - requirements: Annotated[ - Requirements20 | None, - Field( - description='Requirements for JavaScript creative assets. These define the execution environment constraints that the JavaScript must be compatible with.', - title='JavaScript Asset Requirements', - ), - ] = None - - -class Assets25(BaseGroupAsset): - asset_type: Literal['zip'] = 'zip' - - -class Assets26(BaseGroupAsset): - asset_type: Literal['vast'] = 'vast' - requirements: Annotated[ - Requirements21 | None, - Field( - description='Requirements for VAST (Video Ad Serving Template) creative assets.', - title='VAST Asset Requirements', - ), - ] = None - - -class Assets27(BaseGroupAsset): - asset_type: Literal['daast'] = 'daast' - requirements: Annotated[ - Requirements22 | None, - Field( - description='Requirements for DAAST (Digital Audio Ad Serving Template) creative assets.', - title='DAAST Asset Requirements', - ), - ] = None - - -class Assets28(BaseGroupAsset): - asset_type: Literal['url'] = 'url' - requirements: Annotated[ - Requirements23 | None, - Field( - description='Requirements for URL assets such as click-through URLs, tracking pixels, and landing pages.', - title='URL Asset Requirements', - ), - ] = None - - -class Assets29(BaseGroupAsset): - asset_type: Literal['webhook'] = 'webhook' - requirements: Annotated[ - Requirements24 | None, - Field( - description='Requirements for webhook creative assets.', - title='Webhook Asset Requirements', - ), - ] = None - - -class Assets16( - RootModel[ - Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22 - | Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - ] -): - root: Annotated[ - Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22 - | Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29, - Field(discriminator='asset_type'), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets15(AdCPBaseModel): - item_type: Annotated[ - Literal['repeatable_group'], - Field(description='Discriminator indicating this is a repeatable asset group'), - ] = 'repeatable_group' - asset_group_id: Annotated[ - str, Field(description="Identifier for this asset group (e.g., 'product', 'slide', 'card')") - ] - required: Annotated[ - bool, - Field( - description='Whether this asset group is required. If true, at least min_count repetitions must be provided.' - ), - ] - min_count: Annotated[ - int, - Field( - description='Minimum number of repetitions required (if group is required) or allowed (if optional)', - ge=0, - ), - ] - max_count: Annotated[int, Field(description='Maximum number of repetitions allowed', ge=1)] - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="How the platform uses repetitions of this group. 'sequential' means all items display in order (carousels, playlists). 'optimize' means the platform selects the best-performing combination from alternatives (asset group optimization like Meta Advantage+ or Google Pmax)." - ), - ] = SelectionMode.sequential - assets: Annotated[ - list[Assets16], Field(description='Assets within each repetition of this group') - ] - - -class DisclosureCapability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - position: DisclosurePosition - persistence: Annotated[ - list[PersistenceEnum], - Field(description='Persistence modes this position supports', min_length=1), - ] - - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType1, - Field( - description="Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." - ), - ] - required: Annotated[ - bool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - int | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - int | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - int | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - int | None, - Field( - description="Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot1(Slot): - pass - - -class Params1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot1] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection1] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params1, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot2(Slot): - pass - - -class Params2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot2] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection2] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class CanonicalParameters2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params2, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot3(Slot): - pass - - -class Params3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot3] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection3] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params3, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot4(Slot): - pass - - -class Params4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot4] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection4] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[Codec] | None = None - audio_codecs: list[AudioCodec3] | None = None - containers: list[Container3] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params4, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot5(Slot): - pass - - -class Params5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot5] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection5] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class CanonicalParameters5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params5, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot6(Slot): - pass - - -class Params6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot6] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection6] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[Channel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class CanonicalParameters6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params6, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot7(Slot): - pass - - -class Params7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot7] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection7] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class CanonicalParameters7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params7, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot8(Slot): - pass - - -class Params8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot8] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection8] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class CanonicalParameters8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params8, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot9(Slot): - pass - - -class Params9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot9] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection9] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat1] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource4 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource4.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class CanonicalParameters9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params9, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot10(Slot): - pass - - -class Params10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot10] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection10] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class CanonicalParameters10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params10, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot11(Slot): - pass - - -class Params11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot11] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection11] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class CanonicalParameters11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[AppliesToChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params11, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class Format(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="This format's own identifier — a structured object {agent_url, id}, not a string. See /schemas/core/format-id.json for the full shape.", - title='Format Reference (Structured Object)', - ), - ] - name: Annotated[str, Field(description='Human-readable format name')] - description: Annotated[ - str | None, - Field( - description='Plain text explanation of what this format does and what assets it requires' - ), - ] = None - example_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to showcase page with examples and interactive demos of this format' - ), - ] = None - accepts_parameters: Annotated[ - list[AcceptsParameter] | None, - Field( - description='List of parameters this format accepts in format_id. Template formats define which parameters (dimensions, duration, etc.) can be specified when instantiating the format. Empty or omitted means this is a concrete format with fixed parameters.' - ), - ] = None - renders: Annotated[ - list[Renders | Renders1] | None, - Field( - description='Specification of rendered pieces for this format. Most formats produce a single render. Companion ad formats (video + banner), adaptive formats, and multi-placement formats produce multiple renders. Each render specifies its role and dimensions.', - min_length=1, - ), - ] = None - assets: Annotated[ - list[ - Assets - | Assets1 - | Assets2 - | Assets3 - | Assets4 - | Assets5 - | Assets6 - | Assets7 - | Assets8 - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - ] - | None, - Field( - description="Array of all assets supported for this format. Each asset is identified by its asset_id, which must be used as the key in creative manifests. Use the 'required' boolean on each asset to indicate whether it's mandatory." - ), - ] = None - delivery: Annotated[ - dict[str, Any] | None, - Field(description='Delivery method specifications (e.g., hosted, VAST, third-party tags)'), - ] = None - supported_macros: Annotated[ - list[SupportedMacros | str] | None, - Field( - description='List of universal macros supported by this format (e.g., MEDIA_BUY_ID, CACHEBUSTER, DEVICE_ID). Used for validation and developer tooling. See docs/creative/universal-macros.mdx for full documentation.' - ), - ] = None - input_format_ids: Annotated[ - list[InputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `input_format_ids`/`output_format_ids`, so build capability is a property of the transformer (the unit you select and that carries pricing), not a relationship hung on a format. Discover build capability via `list_transformers` (optionally filtered by `input_format_ids`/`output_format_ids`).\n\nMigration: sellers that expressed transform capability by hanging `input_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead. Buyers SHOULD discover build capability via `list_transformers` rather than filtering formats.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format accepts as input creative manifests; when present, indicates this format can take existing creatives in these formats as input. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - output_format_ids: Annotated[ - list[OutputFormatId] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `list_transformers` instead — a transformer declares its own `output_format_ids`, so what a builder can produce is a property of the transformer, not a relationship hung on a format. Discover via `list_transformers`.\n\nMigration: sellers that expressed multi-output build capability (e.g. a multi-publisher template) by hanging `output_format_ids` on a format SHOULD declare a transformer via `list_transformers` instead.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* array of format IDs this format can produce as output; when present, indicates this format can build creatives in these output formats. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - ), - ] = None - format_card: Annotated[ - FormatCard | None, - Field( - description='Optional standard visual card (300x400px) for displaying this format in user interfaces. Can be rendered via preview_creative or pre-generated.' - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field( - description='Accessibility posture of this format. Declares the WCAG conformance level that creatives produced by this format will meet.' - ), - ] = None - supported_disclosure_positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Disclosure positions this format can render. Buyers use this to determine whether a format can satisfy their compliance requirements before submitting a creative. When omitted, the format makes no disclosure rendering guarantees — creative agents SHOULD treat this as incompatible with briefs that require specific disclosure positions. Values correspond to positions on creative-brief.json required_disclosures.', - min_length=1, - ), - ] = None - disclosure_capabilities: Annotated[ - list[DisclosureCapability] | None, - Field( - description='Structured disclosure capabilities per position with persistence modes. Declares which persistence behaviors each disclosure position supports, enabling persistence-aware matching against provenance render guidance and brief requirements. When present, supersedes supported_disclosure_positions for persistence-aware queries. The flat supported_disclosure_positions field is retained for backward compatibility. Each position MUST appear at most once; validators and agents SHOULD reject duplicates.', - min_length=1, - ), - ] = None - format_card_detailed: Annotated[ - FormatCardDetailed | None, - Field( - description='Optional detailed card with carousel and full specifications. Provides rich format documentation similar to ad spec pages.' - ), - ] = None - reported_metrics: Annotated[ - list[ReportedMetric] | None, - Field( - description='Metrics this format can produce in delivery reporting. Buyers receive the intersection of format reported_metrics and product available_metrics. If omitted, the format defers entirely to product-level metric declarations.', - min_length=1, - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - deprecated=True, - description='**DEPRECATED in 3.1. Removed at 4.0.** Use `transformer.pricing_options` (via `list_transformers`) instead — pricing belongs on the transformer (the unit selected and billed), exactly as it belongs on a media-buy product. Once formats only describe output shape, format-level pricing is vestigial.\n\nMigration: transformation/generation agents that charged via `format.pricing_options` SHOULD move the same `vendor-pricing-option` entries onto the corresponding transformer. The applied option is echoed per-leaf on the build_creative response and reconciled via report_usage, unchanged.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* pricing options for this format, used by transformation/generation agents that charge per format adapted, per image generated, or per unit of work; present when the request included include_pricing=true and account. SDKs reading 3.1 catalogs MUST continue to honor this field when present; 4.0+ SDKs MAY reject it. New code SHOULD NOT emit this field.', - min_length=1, - ), - ] = None - canonical: Annotated[ - Canonical | None, - Field( - description='Optional v2 canonical-projection annotation. Always an object — bare-string shorthand (`canonical: "image"`) is not supported; the minimal form is `canonical: { "kind": "image" }`. Carries `kind` (which canonical the v1 format projects to) plus optional `asset_source` and `slots_override` for cases where the v1 format\'s shape doesn\'t follow the canonical\'s defaults (e.g., generative entries whose input is `generation_prompt: text` instead of `image_main: image`).\n\nWhen set, SDKs use this annotation as the authoritative v1 → v2 mapping for this format, bypassing the [v1 canonical mapping registry](/schemas/registries/v1-canonical-mapping.json) lookup. Combined with the slot-level `asset_group_id` declarations on each `assets[i]` entry, a v1 format declaration with `canonical` set is fully self-describing for v1↔v2 translation.\n\nResolution order for SDK projection from v1 wire shape to v2 (per RFC #3305 amendment #3767):\n1. If this `canonical` field is set, use it (seller-declared, highest priority). Apply `asset_source` and `slots_override` from the projection ref when present; otherwise inherit the canonical\'s defaults.\n2. Else, look up `format_id` in the canonical mapping registry\'s `format_id_glob` entries.\n3. Else, attempt structural match against the registry\'s `structural` entries (asset types, slot shape, vast_versions, etc.).\n4. Else, fail closed: SDK MUST NOT emit `format_options` for products carrying this format. Surface `FORMAT_PROJECTION_FAILED` on the response `errors[]` suggesting the seller add an explicit `canonical` annotation or file a registry entry.\n\nWhen `canonical.kind` is `custom`, the seller MUST also declare `canonical_format_shape` and `canonical_format_schema` (parallel to ProductFormatDeclaration\'s `format_shape` and `format_schema`) so buyer SDKs can fetch the seller\'s custom format schema.\n\nSee `canonical-projection-ref.json` for full projection semantics and examples (default-slot case, generative case, brief-driven case).', - examples=[ - {'kind': 'image'}, - { - 'kind': 'image', - 'asset_source': 'agent_synthesized', - 'slots_override': [ - { - 'asset_group_id': 'generation_prompt', - 'asset_type': 'text', - 'required': True, - } - ], - }, - { - 'kind': 'video_hosted', - 'asset_source': 'seller_pre_rendered_from_brief', - 'slots_override': [ - { - 'asset_group_id': 'creative_brief', - 'asset_type': 'brief', - 'required': True, - } - ], - }, - ], - title='Canonical Projection Reference', - ), - ] = None - canonical_parameters: Annotated[ - CanonicalParameters - | CanonicalParameters1 - | CanonicalParameters2 - | CanonicalParameters3 - | CanonicalParameters4 - | CanonicalParameters5 - | CanonicalParameters6 - | CanonicalParameters7 - | CanonicalParameters8 - | CanonicalParameters9 - | CanonicalParameters10 - | CanonicalParameters11 - | CanonicalParameters12 - | None, - Field( - deprecated=True, - description="**DEPRECATED in 3.1. Removed at 4.0.** Use `v1_format_ref` on the v2 `ProductFormatDeclaration` instead — the seller authors a v2 declaration (in `Product.format_options` or `creative.supported_formats`) and links it back to this v1 format via `v1_format_ref: { agent_url, id }`. The directional link from v2 → v1 is the same fact as `canonical_parameters` without the parallel-shape drift surface (v1 file and `canonical_parameters` were two declarations of the same thing; hand-authored, drifting silently).\n\nMigration: every seller currently authoring `canonical_parameters` SHOULD migrate to authoring a v2 declaration on the corresponding product (or capability) with `v1_format_ref` pointing back at this v1 format. v1 files become pure v1 again — no v2-shape mirroring.\n\n*Legacy behavior, retained for 3.1–3.x backward compatibility:* When `canonical` is set, this field carries the full ProductFormatDeclaration that the SDK projects this v1 format into. The `format_kind` MUST equal the `canonical` field value (validators enforce). When set, this is the authoritative source for SDK v1→v2 projection — the registry's structural-match parameter inference is bypassed. SDKs reading 3.1 catalogs MUST continue to honor `canonical_parameters` when present; 4.0+ SDKs MAY reject the field. New code SHOULD NOT emit this field.\n\n**Drift contract (still normative while supported).** Hand-authored `canonical_parameters` MUST satisfy the *narrows* relation against this v1 format's `requirements` and `assets[*]` shape (see canonical-formats.mdx 'Narrows — formal definition'). SDKs that read this v1 file SHOULD lint-time check the equivalence at build/load and emit `FORMAT_PROJECTION_FAILED` if the two disagree.", - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] = None - - -class ListCreativeFormatsResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - formats: Annotated[ - list[Format], - Field( - description="Full format definitions for all formats this agent supports. Each format's authoritative source is indicated by its agent_url field." - ), - ] - source: Annotated[ - Source1 | None, - Field( - description="Which tier of the resolution order produced this `formats[]` list when the request carried a `publisher_domain` filter. `publisher`: agent fetched `/.well-known/adagents.json` and returned its `formats[]` directly (publisher-authoritative). `aao_mirror`: publisher's hosted file was 404 / lacked `formats[]`, agent fell back to `https://creative.adcontextprotocol.org/translated//adagents.json` (community-curated; lower authority — buyer SHOULD treat as advisory until platform adopts). `agent_derived`: neither tier 1 nor tier 2 returned a catalog, so the agent synthesized `formats[]` from the union of its own products' `format_options[]` for products selling the publisher's inventory (lowest authority — agent's view of what it sells, not the publisher's catalog). When two SDKs query the same agent for the same publisher and the agent-derived tier is in play, results may diverge by product set; buyers SHOULD record `source` for telemetry. When the request did NOT carry `publisher_domain`, this field MAY be omitted." - ), - ] = None - creative_agents: Annotated[ - list[CreativeAgent] | None, - Field( - description='Optional: Creative agents that provide additional formats. Buyers can recursively query these agents to discover more formats. No authentication required for list_creative_formats.' - ), - ] = None - errors: Annotated[ - list[Error] | None, - Field(description='Task-specific errors and warnings (e.g., format availability issues)'), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/log_event_request.py b/src/adcp/types/generated_poc/bundled/media_buy/log_event_request.py deleted file mode 100644 index 5ee8b9e6..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/log_event_request.py +++ /dev/null @@ -1,362 +0,0 @@ -# generated by datamodel-codegen: -# filename: log_event_request.json -# timestamp: 2026-06-12T11:08:15+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class Type(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' - - -class Uid(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Type, Field(description='Universal ID type', title='UID Type')] - value: Annotated[str, Field(description='Universal ID value')] - - -class UserMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uids: Annotated[ - list[Uid] | None, Field(description='Universal ID values for user matching', min_length=1) - ] = None - hashed_email: Annotated[ - str | None, - Field( - description='SHA-256 hash of lowercase, trimmed email address. Buyer must normalize before hashing: lowercase, trim whitespace. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - hashed_phone: Annotated[ - str | None, - Field( - description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Buyer must normalize to E.164 before hashing. Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - click_id: Annotated[ - str | None, - Field(description='Platform click identifier (fbclid, gclid, ttclid, ScCid, etc.)'), - ] = None - click_id_type: Annotated[ - str | None, - Field(description='Type of click identifier (e.g. fbclid, gclid, ttclid, msclkid, ScCid)'), - ] = None - client_ip: Annotated[ - str | None, Field(description='Client IP address for probabilistic matching') - ] = None - client_user_agent: Annotated[ - str | None, Field(description='Client user agent string for probabilistic matching') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Content(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[str, Field(description='Product or content identifier')] - quantity: Annotated[int | None, Field(description='Quantity of this item', ge=1)] = None - price: Annotated[float | None, Field(description='Price per unit of this item', ge=0.0)] = None - brand: Annotated[str | None, Field(description='Brand name of this item')] = None - - -class CustomData(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - value: Annotated[ - float | None, - Field( - description='Monetary value of the event (should be accompanied by currency)', ge=0.0 - ), - ] = None - currency: Annotated[ - str | None, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$') - ] = None - order_id: Annotated[str | None, Field(description='Unique order or transaction identifier')] = ( - None - ) - content_ids: Annotated[ - list[str] | None, - Field( - description="Item identifiers for catalog attribution. Values are matched against catalog items using the identifier type declared by the catalog's content_id_type field (e.g., SKUs, GTINs, or vertical-specific IDs like job_id)." - ), - ] = None - content_type: Annotated[ - str | None, - Field( - description="Category of content associated with the event (e.g., 'product', 'job', 'hotel'). Corresponds to the catalog type when used for catalog attribution." - ), - ] = None - content_name: Annotated[str | None, Field(description='Name of the product or content')] = None - content_category: Annotated[ - str | None, Field(description='Category of the product or content') - ] = None - num_items: Annotated[int | None, Field(description='Number of items in the event', ge=0)] = None - search_string: Annotated[str | None, Field(description='Search query for search events')] = None - progress_percent: Annotated[ - float | None, - Field( - description='Content progress percentage reached, primarily for `watch_milestone` events. Use 25, 50, 75, or 100 for quartiles, or another publisher-defined threshold. Do not use `value` for non-monetary progress.', - ge=0.0, - le=100.0, - ), - ] = None - progress_seconds: Annotated[ - float | None, - Field( - description='Content progress duration reached in seconds, primarily for `watch_milestone` events. Use when the milestone is time-based rather than percentage-based.', - ge=0.0, - ), - ] = None - contents: Annotated[ - list[Content] | None, Field(description='Per-item details for e-commerce events') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class Category(StrEnum): - owned_property = 'owned_property' - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class Surface(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - category: Annotated[ - Category, - Field( - description='Generic surface category. `owned_property` covers durable creator or brand-controlled properties hosted by a platform, such as channels, profiles, feeds, lists, podcasts, or playlists.' - ), - ] - property_type: Annotated[ - str | None, - Field( - description='Open vocabulary describing the kind of property, for example `channel`, `profile`, `feed`, `list`, `podcast`, `playlist`, or `newsletter`. Required by convention when `category` is `owned_property`; optional for other categories.', - max_length=128, - min_length=1, - ), - ] = None - namespace: Annotated[ - str | None, - Field( - description='Platform, publisher, or system namespace for the property, such as `video_platform`, `short_video_app`, `audio_service`, or a seller-defined namespace. This is intentionally not an enum.', - max_length=128, - min_length=1, - ), - ] = None - property_id: Annotated[ - str | None, - Field( - description='Optional identifier for the property within `namespace`.', - max_length=256, - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Event(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_id: Annotated[ - str, - Field( - description='Unique identifier for deduplication (scoped to event_type + event_source_id)', - max_length=256, - min_length=1, - ), - ] - event_type: Annotated[EventType, Field(description='Standard event type', title='Event Type')] - event_time: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when the event occurred') - ] - user_match: Annotated[ - UserMatch | None, - Field(description='User identifiers for attribution matching', title='User Match'), - ] = None - custom_data: Annotated[ - CustomData | None, - Field( - description='Event-specific data (value, currency, items, etc.)', - title='Event Custom Data', - ), - ] = None - action_source: Annotated[ - ActionSource | None, Field(description='Where the event originated', title='Action Source') - ] = None - surface: Annotated[ - Surface | None, - Field( - description='Optional structured surface context for the event, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements `action_source`; use it when optimization-relevant meaning would otherwise live only in platform-specific `ext` metadata.', - title='Event Surface', - ), - ] = None - event_source_url: Annotated[ - AnyUrl | None, - Field( - description="URL where the event occurred (required when action_source is 'website')" - ), - ] = None - custom_event_name: Annotated[ - str | None, Field(description="Name for custom events (used when event_type is 'custom')") - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LogEventRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - event_source_id: Annotated[ - str, Field(description='Event source configured on the account via sync_event_sources') - ] - test_event_code: Annotated[ - str | None, - Field( - description="Test event code for validation without affecting production data. Events with this code appear in the platform's test events UI." - ), - ] = None - events: Annotated[ - list[Event], Field(description='Events to log', max_length=10000, min_length=1) - ] - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate event logging on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/log_event_response.py b/src/adcp/types/generated_poc/bundled/media_buy/log_event_response.py deleted file mode 100644 index e8aaa757..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/log_event_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: log_event_response.json -# timestamp: 2026-06-18T11:30:57+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class LogEventResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/package_request.py b/src/adcp/types/generated_poc/bundled/media_buy/package_request.py deleted file mode 100644 index 451b7326..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/package_request.py +++ /dev/null @@ -1,24048 +0,0 @@ -# generated by datamodel-codegen: -# filename: package_request.json -# timestamp: 2026-06-18T11:31:09+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatOptionRefs1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRefs2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class FormatOptionRefs(RootModel[FormatOptionRefs1 | FormatOptionRefs2]): - root: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)] - - -class Target3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - float, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class PostClick(Window): - pass - - -class PostView(Window): - pass - - -class Model(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class AttributionWindow(AdCPBaseModel): - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: Annotated[ - Model | None, - Field( - description="Attribution model used to assign credit when multiple touchpoints exist. SHOULD be populated when committing to a specific model; when absent, the seller's default applies.", - title='Attribution Model', - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Target5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class GeoCountry(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas4(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude1(GeoPostalAreas1): - pass - - -class GeoPostalAreasExclude2(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude3(GeoPostalAreas3): - pass - - -class GeoPostalAreasExclude4(GeoPostalAreas4): - pass - - -class GeoPostalAreasExclude5(GeoPostalAreas5): - pass - - -class GeoPostalAreasExclude6(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude7(GeoPostalAreas7): - pass - - -class GeoPostalAreasExclude8(GeoPostalAreas8): - pass - - -class GeoPostalAreasExclude9(GeoPostalAreas9): - pass - - -class GeoPostalAreasExclude10(GeoPostalAreas10): - pass - - -class Day(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[Day], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Signal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Signal4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal5(Signal1, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal6(Signal2, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal7(Signal3, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal5 | Signal6 | Signal7]): - root: Annotated[ - Signal5 | Signal6 | Signal7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting(RootModel[SignalTargeting1 | SignalTargeting2 | SignalTargeting3]): - root: Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress(Window): - pass - - -class Window1(Window): - pass - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class AcceptedMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AcceptedMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class DevicePlatformEnum(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Unit5(StrEnum): - min = 'min' - hr = 'hr' - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: Annotated[ - Unit5, - Field( - description='Time unit for isochrone (travel-time catchment) calculations.', - title='Travel Time Unit', - ), - ] - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class Unit6(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: Annotated[Unit6, Field(description='Distance unit.', title='Distance Unit')] - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: Annotated[ - TransportMode | None, - Field( - description='Transportation mode for isochrone calculation. Required when travel_time is provided.', - title='Transport Mode', - ), - ] = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class AvailableRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[AvailableRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class Metric1(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class MetricId(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class AttributionWindow1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight for this creative (0–100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description="Optional array of structured placement references where this creative should run within the already-purchased package inventory. New senders SHOULD use this field for placement-level creative routing because placement IDs are publisher-scoped. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. References entries from the product's `placements[]` array by `{ publisher_domain, placement_id }`; if `publisher_domain` is omitted in the ref, receivers MAY interpret it relative to the seller agent's own publisher domain in legacy single-publisher contexts. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description="Legacy shorthand array of placement IDs where this creative should run within the already-purchased package inventory. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. Receivers MAY interpret string IDs relative to the seller agent's own publisher domain in legacy single-publisher contexts. If `placement_refs` is also present, receivers MUST ignore this field.", - min_length=1, - ), - ] = None - - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role18(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role18, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction18(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class FeedFieldMapping1(FeedFieldMapping): - pass - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class Target7(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent1): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class Target8(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent1): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class DeclaredBy26(DeclaredBy): - pass - - -class VerifyAgent52(VerifyAgent): - pass - - -class VerifyAgent53(VerifyAgent1): - pass - - -class VerificationItem26(VerificationItem): - pass - - -class DeclaredBy27(DeclaredBy): - pass - - -class VerifyAgent54(VerifyAgent): - pass - - -class VerifyAgent55(VerifyAgent1): - pass - - -class VerificationItem27(VerificationItem): - pass - - -class DeclaredBy28(DeclaredBy): - pass - - -class VerifyAgent56(VerifyAgent): - pass - - -class VerifyAgent57(VerifyAgent1): - pass - - -class VerificationItem28(VerificationItem): - pass - - -class DeclaredBy29(DeclaredBy): - pass - - -class VerifyAgent58(VerifyAgent): - pass - - -class VerifyAgent59(VerifyAgent1): - pass - - -class VerificationItem29(VerificationItem): - pass - - -class DeclaredBy30(DeclaredBy): - pass - - -class VerifyAgent60(VerifyAgent): - pass - - -class VerifyAgent61(VerifyAgent1): - pass - - -class VerificationItem30(VerificationItem): - pass - - -class DeclaredBy31(DeclaredBy): - pass - - -class VerifyAgent62(VerifyAgent): - pass - - -class VerifyAgent63(VerifyAgent1): - pass - - -class VerificationItem31(VerificationItem): - pass - - -class DeclaredBy32(DeclaredBy): - pass - - -class VerifyAgent64(VerifyAgent): - pass - - -class VerifyAgent65(VerifyAgent1): - pass - - -class VerificationItem32(VerificationItem): - pass - - -class DeclaredBy33(DeclaredBy): - pass - - -class VerifyAgent66(VerifyAgent): - pass - - -class VerifyAgent67(VerifyAgent1): - pass - - -class VerificationItem33(VerificationItem): - pass - - -class DeclaredBy34(DeclaredBy): - pass - - -class VerifyAgent68(VerifyAgent): - pass - - -class VerifyAgent69(VerifyAgent1): - pass - - -class VerificationItem34(VerificationItem): - pass - - -class DeclaredBy35(DeclaredBy): - pass - - -class VerifyAgent70(VerifyAgent): - pass - - -class VerifyAgent71(VerifyAgent1): - pass - - -class VerificationItem35(VerificationItem): - pass - - -class DeclaredBy36(DeclaredBy): - pass - - -class VerifyAgent72(VerifyAgent): - pass - - -class VerifyAgent73(VerifyAgent1): - pass - - -class VerificationItem36(VerificationItem): - pass - - -class DeclaredBy37(DeclaredBy): - pass - - -class VerifyAgent74(VerifyAgent): - pass - - -class VerifyAgent75(VerifyAgent1): - pass - - -class VerificationItem37(VerificationItem): - pass - - -class DeclaredBy38(DeclaredBy): - pass - - -class VerifyAgent76(VerifyAgent): - pass - - -class VerifyAgent77(VerifyAgent1): - pass - - -class VerificationItem38(VerificationItem): - pass - - -class DeclaredBy39(DeclaredBy): - pass - - -class VerifyAgent78(VerifyAgent): - pass - - -class VerifyAgent79(VerifyAgent1): - pass - - -class VerificationItem39(VerificationItem): - pass - - -class ReferenceAsset1(ReferenceAsset): - pass - - -class FeedFieldMapping2(FeedFieldMapping): - pass - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class DeclaredBy40(DeclaredBy): - pass - - -class VerifyAgent80(VerifyAgent): - pass - - -class VerifyAgent81(VerifyAgent1): - pass - - -class VerificationItem40(VerificationItem): - pass - - -class DeclaredBy41(DeclaredBy): - pass - - -class VerifyAgent82(VerifyAgent): - pass - - -class VerifyAgent83(VerifyAgent1): - pass - - -class VerificationItem41(VerificationItem): - pass - - -class DeclaredBy42(DeclaredBy): - pass - - -class VerifyAgent84(VerifyAgent): - pass - - -class VerifyAgent85(VerifyAgent1): - pass - - -class VerificationItem42(VerificationItem): - pass - - -class DeclaredBy43(DeclaredBy): - pass - - -class VerifyAgent86(VerifyAgent): - pass - - -class VerifyAgent87(VerifyAgent1): - pass - - -class VerificationItem43(VerificationItem): - pass - - -class DeclaredBy44(DeclaredBy): - pass - - -class VerifyAgent88(VerifyAgent): - pass - - -class VerifyAgent89(VerifyAgent1): - pass - - -class VerificationItem44(VerificationItem): - pass - - -class DeclaredBy45(DeclaredBy): - pass - - -class VerifyAgent90(VerifyAgent): - pass - - -class VerifyAgent91(VerifyAgent1): - pass - - -class VerificationItem45(VerificationItem): - pass - - -class DeclaredBy46(DeclaredBy): - pass - - -class VerifyAgent92(VerifyAgent): - pass - - -class VerifyAgent93(VerifyAgent1): - pass - - -class VerificationItem46(VerificationItem): - pass - - -class DeclaredBy47(DeclaredBy): - pass - - -class VerifyAgent94(VerifyAgent): - pass - - -class VerifyAgent95(VerifyAgent1): - pass - - -class VerificationItem47(VerificationItem): - pass - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to apply for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class Status2(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class Type1(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type1, - Field( - description='Industry-standard or market-specific identifier types for advertising creatives. These identifiers are managed by external registries or clearance bodies and are used across the supply chain to track and reference specific creative assets. This enum is intentionally closed: add a PR for additional shared identifier schemes rather than using an escape hatch.', - examples=['ad_id', 'isci', 'clearcast_clock', 'idcrea'], - title='Creative Identifier Type', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class DeclaredBy48(DeclaredBy): - pass - - -class VerifyAgent96(VerifyAgent): - pass - - -class VerifyAgent97(VerifyAgent1): - pass - - -class VerificationItem48(VerificationItem): - pass - - - -class DeclaredBy49(DeclaredBy): - pass - - -class VerifyAgent98(VerifyAgent): - pass - - -class VerifyAgent99(VerifyAgent1): - pass - - -class VerificationItem49(VerificationItem): - pass - - -class DeclaredBy50(DeclaredBy): - pass - - -class VerifyAgent100(VerifyAgent): - pass - - -class VerifyAgent101(VerifyAgent1): - pass - - -class VerificationItem50(VerificationItem): - pass - - -class DeclaredBy51(DeclaredBy): - pass - - -class VerifyAgent102(VerifyAgent): - pass - - -class VerifyAgent103(VerifyAgent1): - pass - - -class VerificationItem51(VerificationItem): - pass - - -class DeclaredBy52(DeclaredBy): - pass - - -class VerifyAgent104(VerifyAgent): - pass - - -class VerifyAgent105(VerifyAgent1): - pass - - -class VerificationItem52(VerificationItem): - pass - - -class DeclaredBy53(DeclaredBy): - pass - - -class VerifyAgent106(VerifyAgent): - pass - - -class VerifyAgent107(VerifyAgent1): - pass - - -class VerificationItem53(VerificationItem): - pass - - -class DeclaredBy54(DeclaredBy): - pass - - -class VerifyAgent108(VerifyAgent): - pass - - -class VerifyAgent109(VerifyAgent1): - pass - - -class VerificationItem54(VerificationItem): - pass - - -class DeclaredBy55(DeclaredBy): - pass - - -class VerifyAgent110(VerifyAgent): - pass - - -class VerifyAgent111(VerifyAgent1): - pass - - -class VerificationItem55(VerificationItem): - pass - - -class DeclaredBy56(DeclaredBy): - pass - - -class VerifyAgent112(VerifyAgent): - pass - - -class VerifyAgent113(VerifyAgent1): - pass - - -class VerificationItem56(VerificationItem): - pass - - -class DeclaredBy57(DeclaredBy): - pass - - -class VerifyAgent114(VerifyAgent): - pass - - -class VerifyAgent115(VerifyAgent1): - pass - - -class VerificationItem57(VerificationItem): - pass - - -class DeclaredBy58(DeclaredBy): - pass - - -class VerifyAgent116(VerifyAgent): - pass - - -class VerifyAgent117(VerifyAgent1): - pass - - -class VerificationItem58(VerificationItem): - pass - - -class DeclaredBy59(DeclaredBy): - pass - - -class VerifyAgent118(VerifyAgent): - pass - - -class VerifyAgent119(VerifyAgent1): - pass - - -class VerificationItem59(VerificationItem): - pass - - -class DeclaredBy60(DeclaredBy): - pass - - -class VerifyAgent120(VerifyAgent): - pass - - -class VerifyAgent121(VerifyAgent1): - pass - - -class VerificationItem60(VerificationItem): - pass - - -class DeclaredBy61(DeclaredBy): - pass - - -class VerifyAgent122(VerifyAgent): - pass - - -class VerifyAgent123(VerifyAgent1): - pass - - -class VerificationItem61(VerificationItem): - pass - - -class DeclaredBy62(DeclaredBy): - pass - - -class VerifyAgent124(VerifyAgent): - pass - - -class VerifyAgent125(VerifyAgent1): - pass - - -class VerificationItem62(VerificationItem): - pass - - -class ReferenceAsset2(ReferenceAsset): - pass - - -class FeedFieldMapping3(FeedFieldMapping): - pass - - -class ReferenceAuthorization2(ReferenceAuthorization): - pass - - -class DeclaredBy63(DeclaredBy): - pass - - -class VerifyAgent126(VerifyAgent): - pass - - -class VerifyAgent127(VerifyAgent1): - pass - - -class VerificationItem63(VerificationItem): - pass - - -class DeclaredBy64(DeclaredBy): - pass - - -class VerifyAgent128(VerifyAgent): - pass - - -class VerifyAgent129(VerifyAgent1): - pass - - -class VerificationItem64(VerificationItem): - pass - - -class DeclaredBy65(DeclaredBy): - pass - - -class VerifyAgent130(VerifyAgent): - pass - - -class VerifyAgent131(VerifyAgent1): - pass - - -class VerificationItem65(VerificationItem): - pass - - -class DeclaredBy66(DeclaredBy): - pass - - -class VerifyAgent132(VerifyAgent): - pass - - -class VerifyAgent133(VerifyAgent1): - pass - - -class VerificationItem66(VerificationItem): - pass - - -class DeclaredBy67(DeclaredBy): - pass - - -class VerifyAgent134(VerifyAgent): - pass - - -class VerifyAgent135(VerifyAgent1): - pass - - -class VerificationItem67(VerificationItem): - pass - - -class DeclaredBy68(DeclaredBy): - pass - - -class VerifyAgent136(VerifyAgent): - pass - - -class VerifyAgent137(VerifyAgent1): - pass - - -class VerificationItem68(VerificationItem): - pass - - -class DeclaredBy69(DeclaredBy): - pass - - -class VerifyAgent138(VerifyAgent): - pass - - -class VerifyAgent139(VerifyAgent1): - pass - - -class VerificationItem69(VerificationItem): - pass - - -class DeclaredBy70(DeclaredBy): - pass - - -class VerifyAgent140(VerifyAgent): - pass - - -class VerifyAgent141(VerifyAgent1): - pass - - -class VerificationItem70(VerificationItem): - pass - - -class DeclaredBy71(DeclaredBy): - pass - - -class VerifyAgent142(VerifyAgent): - pass - - -class VerifyAgent143(VerifyAgent1): - pass - - -class VerificationItem71(VerificationItem): - pass - - -class DeclaredBy72(DeclaredBy): - pass - - -class VerifyAgent144(VerifyAgent): - pass - - -class VerifyAgent145(VerifyAgent1): - pass - - -class VerificationItem72(VerificationItem): - pass - - -class DeclaredBy73(DeclaredBy): - pass - - -class VerifyAgent146(VerifyAgent): - pass - - -class VerifyAgent147(VerifyAgent1): - pass - - -class VerificationItem73(VerificationItem): - pass - - -class DeclaredBy74(DeclaredBy): - pass - - -class VerifyAgent148(VerifyAgent): - pass - - -class VerifyAgent149(VerifyAgent1): - pass - - -class VerificationItem74(VerificationItem): - pass - - -class DeclaredBy75(DeclaredBy): - pass - - -class VerifyAgent150(VerifyAgent): - pass - - -class VerifyAgent151(VerifyAgent1): - pass - - -class VerificationItem75(VerificationItem): - pass - - -class DeclaredBy76(DeclaredBy): - pass - - -class VerifyAgent152(VerifyAgent): - pass - - -class VerifyAgent153(VerifyAgent1): - pass - - -class VerificationItem76(VerificationItem): - pass - - -class DeclaredBy77(DeclaredBy): - pass - - -class VerifyAgent154(VerifyAgent): - pass - - -class VerifyAgent155(VerifyAgent1): - pass - - -class VerificationItem77(VerificationItem): - pass - - -class DeclaredBy78(DeclaredBy): - pass - - -class VerifyAgent156(VerifyAgent): - pass - - -class VerifyAgent157(VerifyAgent1): - pass - - -class VerificationItem78(VerificationItem): - pass - - -class DeclaredBy79(DeclaredBy): - pass - - -class VerifyAgent158(VerifyAgent): - pass - - -class VerifyAgent159(VerifyAgent1): - pass - - -class VerificationItem79(VerificationItem): - pass - - -class DeclaredBy80(DeclaredBy): - pass - - -class VerifyAgent160(VerifyAgent): - pass - - -class VerifyAgent161(VerifyAgent1): - pass - - -class VerificationItem80(VerificationItem): - pass - - -class DeclaredBy81(DeclaredBy): - pass - - -class VerifyAgent162(VerifyAgent): - pass - - -class VerifyAgent163(VerifyAgent1): - pass - - -class VerificationItem81(VerificationItem): - pass - - -class DeclaredBy82(DeclaredBy): - pass - - -class VerifyAgent164(VerifyAgent): - pass - - -class VerifyAgent165(VerifyAgent1): - pass - - -class VerificationItem82(VerificationItem): - pass - - -class DeclaredBy83(DeclaredBy): - pass - - -class VerifyAgent166(VerifyAgent): - pass - - -class VerifyAgent167(VerifyAgent1): - pass - - -class VerificationItem83(VerificationItem): - pass - - -class DeclaredBy84(DeclaredBy): - pass - - -class VerifyAgent168(VerifyAgent): - pass - - -class VerifyAgent169(VerifyAgent1): - pass - - -class VerificationItem84(VerificationItem): - pass - - -class ReferenceAsset3(ReferenceAsset): - pass - - -class FeedFieldMapping4(FeedFieldMapping): - pass - - -class ReferenceAuthorization3(ReferenceAuthorization): - pass - - -class DeclaredBy85(DeclaredBy): - pass - - -class VerifyAgent170(VerifyAgent): - pass - - -class VerifyAgent171(VerifyAgent1): - pass - - -class VerificationItem85(VerificationItem): - pass - - -class DeclaredBy86(DeclaredBy): - pass - - -class VerifyAgent172(VerifyAgent): - pass - - -class VerifyAgent173(VerifyAgent1): - pass - - -class VerificationItem86(VerificationItem): - pass - - -class DeclaredBy87(DeclaredBy): - pass - - -class VerifyAgent174(VerifyAgent): - pass - - -class VerifyAgent175(VerifyAgent1): - pass - - -class VerificationItem87(VerificationItem): - pass - - -class DeclaredBy88(DeclaredBy): - pass - - -class VerifyAgent176(VerifyAgent): - pass - - -class VerifyAgent177(VerifyAgent1): - pass - - -class VerificationItem88(VerificationItem): - pass - - -class DeclaredBy89(DeclaredBy): - pass - - -class VerifyAgent178(VerifyAgent): - pass - - -class VerifyAgent179(VerifyAgent1): - pass - - -class VerificationItem89(VerificationItem): - pass - - -class DeclaredBy90(DeclaredBy): - pass - - -class VerifyAgent180(VerifyAgent): - pass - - -class VerifyAgent181(VerifyAgent1): - pass - - -class VerificationItem90(VerificationItem): - pass - - -class DeclaredBy91(DeclaredBy): - pass - - -class VerifyAgent182(VerifyAgent): - pass - - -class VerifyAgent183(VerifyAgent1): - pass - - -class VerificationItem91(VerificationItem): - pass - - -class DeclaredBy92(DeclaredBy): - pass - - -class VerifyAgent184(VerifyAgent): - pass - - -class VerifyAgent185(VerifyAgent1): - pass - - -class VerificationItem92(VerificationItem): - pass - - -class IndustryIdentifier1(IndustryIdentifier): - pass - - -class DeclaredBy93(DeclaredBy): - pass - - -class VerifyAgent186(VerifyAgent): - pass - - -class VerifyAgent187(VerifyAgent1): - pass - - -class VerificationItem93(VerificationItem): - pass - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class OptimizationGoals1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target1 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: EventType - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When present without a value-oriented target, the seller may use it for delivery reporting (conversion_value, roas) but must not change the optimization objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - float | None, - Field( - description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class OptimizationGoals2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target2 | Target3 | Target4 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target5 | Target6 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals(RootModel[OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3]): - root: Annotated[ - OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas12(GeoPostalAreas1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas13(GeoPostalAreas2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas14(GeoPostalAreas3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas15(GeoPostalAreas4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas16(GeoPostalAreas5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas17(GeoPostalAreas6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas18(GeoPostalAreas7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas19(GeoPostalAreas8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas20(GeoPostalAreas9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas21(GeoPostalAreas10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21 - ] -): - root: Annotated[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude11(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude12(GeoPostalAreasExclude1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude13(GeoPostalAreasExclude2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude14(GeoPostalAreasExclude3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude15(GeoPostalAreasExclude4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude16(GeoPostalAreasExclude5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude17(GeoPostalAreasExclude6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude18(GeoPostalAreasExclude7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude19(GeoPostalAreasExclude8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude20(GeoPostalAreasExclude9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude21(GeoPostalAreasExclude10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21 - ] -): - root: Annotated[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude22(GeoPostalAreas22): - pass - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window1 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas22] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude22] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatformEnum] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor1, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric1, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor2, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: Annotated[ - CompletionSource | None, - Field( - description="Trust-source disambiguator for `completion_rate` — *who* attested to the completion event, not *how* (methodology granularity is a separate dimension; future qualifier keys may add it if buyer demand surfaces). The two paths can yield materially different rates, particularly in SSAI environments where the player's view of completion may differ from a vendor's. Used as a `qualifier.completion_source` key on `committed_metrics`, `missing_metrics`, and `metric_aggregates` to disambiguate which trust source the row represents. Edge cases: walled gardens where the seller is also the measurement vendor (YouTube, Spotify) collapse to `seller_attested` by trust-model logic — the same party served and counted. IAB-certified first-party podcast measurement (Podtrac, Triton on their own platforms; Art19 on its own platform) likewise collapses to `seller_attested`. The same vendor's offering on a third-party platform (Podtrac on a publisher who isn't Podtrac) is `vendor_attested`. The trust axis is *not* who runs the SDK — it's who is independent of the seller's revenue interest.", - title='Completion Source', - ), - ] = None - attribution_methodology: Annotated[ - AttributionMethodology | None, - Field( - description='How attribution between ad exposure and outcome events was computed. Used as a `qualifier.attribution_methodology` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate the same outcome metric reported under different methodologies — `conversion_value` measured deterministically (matched purchase IDs) is not the same number as `conversion_value` measured probabilistically (modeled match) and should never be summed across methodologies. The retail-media closed-loop pattern typically reports under `deterministic_purchase`; MMM and clean-room outputs typically report under `modeled` or `probabilistic`; panel-based measurement (Nielsen, comScore, Edison) reports under `panel_based`.', - title='Attribution Methodology', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow1 | None, - Field( - description="A time duration expressed as an interval and unit. Used for frequency cap windows, attribution windows, reach optimization windows, time budgets, and other time-based settings. When unit is 'campaign', interval must be 1 — the window spans the full campaign flight.", - title='Duration', - ), - ] = None - lift_dimension: Annotated[ - LiftDimension | None, - Field( - description='Brand-lift dimension disambiguator. Brand lift is multidimensional in production — Kantar, Upwave, Cint, DoubleVerify, and similar vendors report awareness, consideration, favorability, purchase intent, and ad recall as separate measurements with their own sample sizes and confidence intervals. Used as a `qualifier.lift_dimension` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate which dimension of `brand_lift` a row represents. Two `brand_lift` rows under different lift dimensions represent different surveyed outcomes and must not be combined into a single number.', - title='Lift Dimension', - ), - ] = None - - -class CommittedMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: Annotated[ - MetricId, - Field( - description="Identifier for the standard metric. MUST be present in the product's `reporting_capabilities.available_metrics`.", - title='Available Metric', - ), - ] - qualifier: Annotated[ - Qualifier | None, - Field( - description='Disambiguator — same shape as on the response-side `committed_metrics`. Required when the buyer wants to pin a specific measurement path: `viewability_standard` for MRC vs GroupM viewability; `completion_source` for seller- vs vendor-attested `completion_rate`; `attribution_methodology` for how attribution was computed (deterministic_purchase, probabilistic, panel_based, modeled); `attribution_window` for the time window over which outcomes are attributed. See response-side description for full semantics.' - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor3, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. MUST be present in the product's `reporting_capabilities.vendor_metrics` for the same vendor.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class CommittedMetrics(RootModel[CommittedMetrics1 | CommittedMetrics2]): - root: Annotated[CommittedMetrics1 | CommittedMetrics2, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction16(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets15(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets16(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping1] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media1, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction26(Jurisdiction): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction26] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent52 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent53 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction27(Jurisdiction): - pass - - -class Disclosure26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction27] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy26 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem26] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark26] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure26 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem26] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance26 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent54 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent55 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction28(Jurisdiction): - pass - - -class Disclosure27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction28] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy27 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem27] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark27] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure27 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem27] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance27 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent56 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent57 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction29(Jurisdiction): - pass - - -class Disclosure28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction29] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy28 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem28] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark28] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure28 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem28] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance28 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent58 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent59 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction30(Jurisdiction): - pass - - -class Disclosure29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction30] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy29 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem29] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark29] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure29 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem29] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance29 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent60 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent61 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction31(Jurisdiction): - pass - - -class Disclosure30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction31] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy30 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem30] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark30] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure30 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem30] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance30 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent62 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent63 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction32(Jurisdiction): - pass - - -class Disclosure31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction32] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy31 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem31] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark31] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure31 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem31] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance31 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent64 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent65 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction33(Jurisdiction): - pass - - -class Disclosure32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction33] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy32 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem32] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark32] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure32 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem32] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance32 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent66 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent67 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction34(Jurisdiction): - pass - - -class Disclosure33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction34] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy33 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem33] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark33] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure33 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem33] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance33 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent68 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent69 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction35(Jurisdiction): - pass - - -class Disclosure34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction35] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy34 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem34] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark34] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure34 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem34] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance34 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent70 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent71 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction36(Jurisdiction): - pass - - -class Disclosure35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction36] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy35 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem35] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark35] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure35 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem35] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance35 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent72 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent73 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction37(Jurisdiction): - pass - - -class Disclosure36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction37] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy36 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem36] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark36] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure36 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem36] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance36 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent74 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent75 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction38(Jurisdiction): - pass - - -class Disclosure37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction38] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy37 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem37] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark37] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure37 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem37] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance37 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent76 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent77 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction39(Jurisdiction): - pass - - -class Disclosure38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction39] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy38 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem38] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark38] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure38 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem38] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance38 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent78 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent79 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction40(Jurisdiction): - pass - - -class Disclosure39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction40] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy39 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem39] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark39] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure39 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem39] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance39 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure1(RequiredDisclosure): - pass - - -class Compliance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure1] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets2217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset1] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance1 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets2218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping2] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent80 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent81 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction42(Jurisdiction): - pass - - -class Disclosure40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction42] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy40 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem40] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark40] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure40 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem40] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance40 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent82 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent83 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction43(Jurisdiction): - pass - - -class Disclosure41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction43] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy41 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem41] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark41] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure41 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem41] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance41 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent84 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent85 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction44(Jurisdiction): - pass - - -class Disclosure42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction44] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy42 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem42] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark42] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure42 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem42] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance42 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent86 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent87 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction45(Jurisdiction): - pass - - -class Disclosure43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction45] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy43 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem43] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark43] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure43 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem43] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance43 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent88 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent89 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction46(Jurisdiction): - pass - - -class Disclosure44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction46] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy44 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem44] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark44] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure44 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem44] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media2 | Media3, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl1 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance44 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent90 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent91 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction47(Jurisdiction): - pass - - -class Disclosure45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction47] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy45 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem45] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark45] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure45 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem45] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance45 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent92 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent93 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction48(Jurisdiction): - pass - - -class Disclosure46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction48] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy46 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem46] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark46] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure46 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem46] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance46 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent94 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent95 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction49(Jurisdiction): - pass - - -class Disclosure47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction49] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy47 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem47] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark47] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure47 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem47] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance47 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets221( - RootModel[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223 - ] -): - root: Annotated[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets22(RootModel[list[Assets221]]): - root: Annotated[list[Assets221], Field(min_length=1)] - - -class EmbeddedProvenanceItem48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent96 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent97 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction50(Jurisdiction): - pass - - -class Disclosure48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction50] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy48 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem48] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark48] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure48 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem48] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets1 - | Assets2 - | Assets3 - | Assets4 - | Assets5 - | Assets6 - | Assets7 - | Assets8 - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - | Assets16 - | Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status2 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance48 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent98 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent99 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction51(Jurisdiction): - pass - - -class Disclosure49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction51] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy49 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem49] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark49] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure49 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem49] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance49 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction52(Jurisdiction): - pass - - -class Disclosure50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction52] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy50 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem50] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark50] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure50 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem50] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance50 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction53(Jurisdiction): - pass - - -class Disclosure51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction53] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy51 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem51] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark51] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure51 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem51] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance51 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction54(Jurisdiction): - pass - - -class Disclosure52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction54] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy52 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem52] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark52] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure52 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem52] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance52 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction55(Jurisdiction): - pass - - -class Disclosure53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction55] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy53 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem53] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark53] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure53 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem53] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance53 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction56(Jurisdiction): - pass - - -class Disclosure54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction56] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy54 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem54] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark54] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure54 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem54] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance54 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction57(Jurisdiction): - pass - - -class Disclosure55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction57] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy55 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem55] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark55] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure55 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem55] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance55 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction58(Jurisdiction): - pass - - -class Disclosure56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction58] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy56 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem56] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark56] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure56 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem56] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance56 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction59(Jurisdiction): - pass - - -class Disclosure57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction59] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy57 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem57] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark57] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure57 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem57] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance57 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction60(Jurisdiction): - pass - - -class Disclosure58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction60] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy58 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem58] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark58] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure58 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem58] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance58 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction61(Jurisdiction): - pass - - -class Disclosure59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction61] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy59 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem59] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark59] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure59 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem59] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance59 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction62(Jurisdiction): - pass - - -class Disclosure60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction62] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy60 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem60] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark60] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure60 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem60] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance60 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction63(Jurisdiction): - pass - - -class Disclosure61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction63] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy61 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem61] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark61] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure61 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem61] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance61 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction64(Jurisdiction): - pass - - -class Disclosure62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction64] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy62 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem62] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark62] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure62 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem62] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance62 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets37(Assets14): - pass - - -class RequiredDisclosure2(RequiredDisclosure): - pass - - -class Compliance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure2] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets38(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset2] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance2 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets39(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping3] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction66(Jurisdiction): - pass - - -class Disclosure63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction66] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy63 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem63] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark63] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure63 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem63] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization2 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance63 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction67(Jurisdiction): - pass - - -class Disclosure64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction67] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy64 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem64] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark64] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure64 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem64] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance64 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction68(Jurisdiction): - pass - - -class Disclosure65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction68] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy65 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem65] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark65] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure65 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem65] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance65 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction69(Jurisdiction): - pass - - -class Disclosure66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction69] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy66 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem66] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark66] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure66 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem66] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance66 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction70(Jurisdiction): - pass - - -class Disclosure67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction70] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy67 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem67] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark67] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure67 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem67] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media4 | Media5, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl2 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance67 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction71(Jurisdiction): - pass - - -class Disclosure68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction71] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy68 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem68] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark68] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure68 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem68] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance68 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction72(Jurisdiction): - pass - - -class Disclosure69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction72] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy69 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem69] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark69] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure69 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem69] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance69 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction73(Jurisdiction): - pass - - -class Disclosure70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction73] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy70 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem70] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark70] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure70 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem70] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance70 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction74(Jurisdiction): - pass - - -class Disclosure71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction74] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy71 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem71] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark71] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure71 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem71] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance71 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction75(Jurisdiction): - pass - - -class Disclosure72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction75] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy72 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem72] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark72] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure72 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem72] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance72 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction76(Jurisdiction): - pass - - -class Disclosure73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction76] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy73 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem73] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark73] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure73 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem73] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance73 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction77(Jurisdiction): - pass - - -class Disclosure74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction77] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy74 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem74] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark74] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure74 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem74] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance74 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction78(Jurisdiction): - pass - - -class Disclosure75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction78] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy75 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem75] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark75] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure75 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem75] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance75 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction79(Jurisdiction): - pass - - -class Disclosure76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction79] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy76 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem76] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark76] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure76 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem76] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance76 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction80(Jurisdiction): - pass - - -class Disclosure77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction80] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy77 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem77] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark77] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure77 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem77] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance77 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction81(Jurisdiction): - pass - - -class Disclosure78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction81] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy78 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem78] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark78] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure78 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem78] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance78 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction82(Jurisdiction): - pass - - -class Disclosure79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction82] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy79 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem79] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark79] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure79 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem79] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance79 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction83(Jurisdiction): - pass - - -class Disclosure80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction83] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy80 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem80] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark80] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure80 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem80] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance80 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction84(Jurisdiction): - pass - - -class Disclosure81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction84] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy81 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem81] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark81] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure81 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem81] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance81 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction85(Jurisdiction): - pass - - -class Disclosure82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction85] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy82 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem82] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark82] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure82 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem82] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance82 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction86(Jurisdiction): - pass - - -class Disclosure83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction86] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy83 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem83] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark83] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure83 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem83] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance83 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction87(Jurisdiction): - pass - - -class Disclosure84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction87] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy84 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem84] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark84] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure84 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem84] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance84 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure3(RequiredDisclosure): - pass - - -class Compliance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure3] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets4517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset3] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance3 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets4518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping4] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction89(Jurisdiction): - pass - - -class Disclosure85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction89] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy85 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem85] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark85] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure85 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem85] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization3 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance85 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction90(Jurisdiction): - pass - - -class Disclosure86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction90] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy86 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem86] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark86] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure86 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem86] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance86 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction91(Jurisdiction): - pass - - -class Disclosure87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction91] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy87 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem87] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark87] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure87 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem87] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance87 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction92(Jurisdiction): - pass - - -class Disclosure88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction92] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy88 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem88] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark88] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure88 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem88] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance88 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction93(Jurisdiction): - pass - - -class Disclosure89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction93] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy89 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem89] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark89] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure89 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem89] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media6 | Media7, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl3 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance89 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction94(Jurisdiction): - pass - - -class Disclosure90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction94] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy90 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem90] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark90] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure90 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem90] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance90 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction95(Jurisdiction): - pass - - -class Disclosure91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction95] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy91 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem91] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark91] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure91 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem91] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance91 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction96(Jurisdiction): - pass - - -class Disclosure92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction96] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy92 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem92] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark92] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure92 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem92] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance92 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets451( - RootModel[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523 - ] -): - root: Annotated[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets45(RootModel[list[Assets451]]): - root: Annotated[list[Assets451], Field(min_length=1)] - - -class EmbeddedProvenanceItem93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction97(Jurisdiction): - pass - - -class Disclosure93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction97] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy93 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem93] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark93] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure93 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem93] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId | None, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - | Assets30 - | Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: Annotated[ - Status2 | None, - Field( - description="For generative creatives: set to 'approved' to finalize, 'rejected' to request regeneration with updated assets/message. Omit for non-generative creatives (system will set based on processing state).", - title='Creative Status', - ), - ] = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier1] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance93 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class PackageRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - product_id: Annotated[ - str, - Field( - description='Product ID for this package. Sellers MUST echo this value on every response package object that represents this requested package.' - ), - ] - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format selector. Array of format IDs that will be used for this package - must be supported by the product. If omitted (and no 3.1+ format-option selector or direct canonical selector is present), defaults to all formats supported by the product.\n\nSellers comparing this selector to a product's `format_options[]` MUST first normalize each legacy `format_id` through the canonical mapping path (`canonical`, `v1_format_ref`, or registry projection). Exact `(agent_url, id)` comparison after projection is insufficient: a legacy fixed-size display ID can satisfy a canonical `image` product declaration with matching `width`/`height`. Product gating remains directional: if the product declares fixed dimensions or duration, the selected format must declare and match those constraints; an under-specified canonical request is not a wildcard for a fixed-size or fixed-duration product. Range constraints use containment, not overlap: a range-based request satisfies the product only when every value it permits falls within the product's accepted range.", - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs] | None, - Field( - description='3.1+ format-option selector. Array of structured format option references, each matching one of the target product\'s `format_options[]` entries. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. If omitted along with `format_ids` and direct `format_kind`, all product formats are active.\n\n**Resolution rules (normative).**\n- **Both `format_option_refs` and `format_ids` present.** `format_option_refs` wins; the seller routes by structured references and MUST NOT validate `format_ids` for consistency with the resolved declarations. The `format_ids` value is a legacy-compat hint for intermediaries on the wire path; the resolving seller ignores it.\n- **`format_option_refs` only.** Seller looks up each entry against the package\'s target product `format_options[]` and uses the matching declaration (and that declaration\'s `v1_format_ref[]` when projecting to legacy named-format surfaces). This is the 3.1+ format-option authoring path. `scope: "product"` is scoped only by this target product; it is not a seller-wide identifier.\n- **`format_ids` only.** Existing named-format behavior; unchanged.\n- **`format_kind` only.** Direct canonical selector behavior; seller compares `{ format_kind, params }` against the product\'s `format_options[]` declarations using directional product satisfaction.\n- **None of `format_option_refs`, `format_ids`, or `format_kind`.** Default — all formats supported by the product are active.\n\n**Failure modes (normative).** Sellers MUST reject with `UNSUPPORTED_FEATURE` (with `field` pointing at the failing package and entry, e.g. `packages[0].format_option_refs[1]`) when:\n- Any entry references a format option not present in the target product\'s `format_options[]`, OR\n- The target product carries `format_ids` but no `format_options[]` (legacy-format-only product — there is no closed set to resolve against), OR\n- The target product carries `format_options[]` but none of the entries publish selectable `format_option_id` values. Sellers SHOULD set `error.details.reason` to `format_option_refs_not_published` in this case so buyers can distinguish it from an outright mismatch and fall back to `format_ids[]`.\n\n**Seller obligation.** For buyers to use the 3.1+ format-option path against a product, the seller MUST publish a selectable `format_option_id` on each `format_options[]` entry it expects buyers to select; if the option is publisher-catalog backed, include `publisher_domain` on the product declaration and require buyers to use `scope: "publisher"` in `FormatOptionRefs1`.\n\n**No legacy capability selector.** `capability_ids` was removed before GA; schemas reject it instead of treating it as an extension.\n\n**Dual emission.** Format-option-aware buyer SDKs targeting a heterogeneous seller population SHOULD emit `format_ids` alongside `format_option_refs` so legacy-format-only sellers — which ignore unknown fields per `additionalProperties: true` — still receive an explicit format set rather than silently defaulting to all formats supported by the product.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Omit when selecting by `format_option_refs` or `format_ids`; those selectors resolve their parameters from the product declaration or legacy catalog projection." - ), - ] = None - budget: Annotated[ - float, - Field(description="Budget allocation for this package in the media buy's currency", ge=0.0), - ] - pacing: Annotated[ - Pacing | None, Field(description='Budget pacing strategy', title='Pacing') - ] = None - pricing_option_id: Annotated[ - str, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - catalogs: Annotated[ - list[Catalog] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' - ), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics] | None, - Field( - description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment] | None, - Field( - description='Assign existing library creatives to this package with optional weights and placement targeting', - min_length=1, - ), - ] = None - creatives: Annotated[ - Sequence[Creatives | Creatives1] | None, - Field( - description="Upload creative assets inline and assign to this package. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', - max_length=100, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_request.py b/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_request.py deleted file mode 100644 index ecedb625..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_request.py +++ /dev/null @@ -1,115 +0,0 @@ -# generated by datamodel-codegen: -# filename: provide_performance_feedback_request.json -# timestamp: 2026-05-22T13:16:43+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AwareDatetime, ConfigDict, Field - - -class MeasurementPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[AwareDatetime, Field(description='Start timestamp (inclusive), ISO 8601')] - end: Annotated[AwareDatetime, Field(description='End timestamp (inclusive), ISO 8601')] - - -class MetricType(StrEnum): - overall_performance = 'overall_performance' - conversion_rate = 'conversion_rate' - brand_lift = 'brand_lift' - click_through_rate = 'click_through_rate' - completion_rate = 'completion_rate' - viewability = 'viewability' - brand_safety = 'brand_safety' - cost_efficiency = 'cost_efficiency' - - -class FeedbackSource(StrEnum): - buyer_attribution = 'buyer_attribution' - third_party_measurement = 'third_party_measurement' - platform_analytics = 'platform_analytics' - verification_partner = 'verification_partner' - - -class ProvidePerformanceFeedbackRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier", min_length=1)] - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate feedback submissions on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - measurement_period: Annotated[ - MeasurementPeriod, - Field(description='Time period for performance measurement', title='Datetime Range'), - ] - performance_index: Annotated[ - float, - Field( - description='Normalized performance score (0.0 = no value, 1.0 = expected, >1.0 = above expected)', - ge=0.0, - ), - ] - package_id: Annotated[ - str | None, - Field( - description='Specific package within the media buy (if feedback is package-specific)', - min_length=1, - ), - ] = None - creative_id: Annotated[ - str | None, - Field( - description='Specific creative asset (if feedback is creative-specific)', min_length=1 - ), - ] = None - metric_type: Annotated[ - MetricType | None, - Field(description='The business metric being measured', title='Metric Type (Deprecated)'), - ] = MetricType.overall_performance - feedback_source: Annotated[ - FeedbackSource | None, - Field(description='Source of the performance data', title='Feedback Source'), - ] = FeedbackSource.buyer_attribution - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_response.py b/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_response.py deleted file mode 100644 index 54cbee8c..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/provide_performance_feedback_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: provide_performance_feedback_response.json -# timestamp: 2026-06-18T11:31:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class ProvidePerformanceFeedbackResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_request.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_request.py deleted file mode 100644 index 0d82098e..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_request.py +++ /dev/null @@ -1,780 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_audiences_request.json -# timestamp: 2026-06-12T11:08:26+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class AudienceType(StrEnum): - crm = 'crm' - suppression = 'suppression' - lookalike_seed = 'lookalike_seed' - - -class Tag(RootModel[str]): - root: Annotated[str, Field(min_length=1)] - - -class ConsentBasis(StrEnum): - consent = 'consent' - legitimate_interest = 'legitimate_interest' - contract = 'contract' - legal_obligation = 'legal_obligation' - - -class UIDType(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' - - -class Uid(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: UIDType - value: Annotated[str, Field(description='Universal ID value')] - - -class AddItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - external_id: Annotated[ - str, - Field( - description="Buyer-assigned stable identifier for this audience member (e.g. CRM record ID, loyalty ID). Used for deduplication, removal, and cross-referencing with buyer systems. Adapters for CDPs that don't natively assign IDs can derive one (e.g. hash of the member's identifiers)." - ), - ] - hashed_email: Annotated[ - str | None, - Field( - description='SHA-256 hash of lowercase, trimmed email address. Pseudonymous PII, not anonymous — the email namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - hashed_phone: Annotated[ - str | None, - Field( - description='SHA-256 hash of E.164-formatted phone number (e.g. +12065551234). Pseudonymous PII, not anonymous — the E.164 namespace is small enough that an unsalted SHA-256 is recoverable via precomputed dictionaries. Treat as PII for retention, consent, and access-control purposes. See docs/reference/privacy-considerations#unsalted-hashed-identifiers-are-pseudonymous-not-anonymous.', - pattern='^[a-f0-9]{64}$', - ), - ] = None - uids: Annotated[ - list[Uid] | None, - Field( - description='Universal ID values (MAIDs, RampID, UID2, etc.) for user matching.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class RemoveItem(AddItem): - pass - - -class Audience(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - audience_id: Annotated[ - str, - Field( - description="Buyer's identifier for this audience. Used to reference the audience in targeting overlays." - ), - ] - name: Annotated[str | None, Field(description='Human-readable name for this audience')] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this audience's composition or purpose (e.g., 'High-value customers who purchased in the last 90 days')." - ), - ] = None - audience_type: Annotated[ - AudienceType | None, - Field( - description="Intended use for this audience. 'crm': target these users. 'suppression': exclude these users from delivery. 'lookalike_seed': use as a seed for the seller's lookalike modeling. Sellers may handle audiences differently based on type (e.g., suppression lists bypass minimum size requirements on some platforms)." - ), - ] = None - tags: Annotated[ - list[Tag] | None, - Field( - description="Buyer-defined tags for organizing and filtering audiences (e.g., 'holiday_2026', 'high_ltv'). Tags are stored by the seller and returned in discovery-only calls." - ), - ] = None - add: Annotated[ - list[AddItem] | None, - Field( - description='Members to add to this audience. Hashed before sending — normalize emails to lowercase+trim, phones to E.164.', - min_length=1, - ), - ] = None - remove: Annotated[ - list[RemoveItem] | None, - Field( - description='Members to remove from this audience. If the same identifier appears in both add and remove in a single request, remove takes precedence.', - min_length=1, - ), - ] = None - delete: Annotated[ - bool | None, - Field( - description='When true, delete this audience from the account entirely. All other fields on this audience object are ignored. Use this to delete a specific audience without affecting others.' - ), - ] = None - consent_basis: Annotated[ - ConsentBasis | None, - Field( - description='GDPR lawful basis for processing this audience list. Informational — not validated by the protocol, but required by some sellers operating in regulated markets (e.g. EU). When omitted, the buyer asserts they have a lawful basis appropriate to their jurisdiction.', - title='Consent Basis', - ), - ] = None - - -class SyncAudiencesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `audience_id` gives resource-level dedup per audience, but the sync envelope emits audit events and may trigger downstream refreshes — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `audiences` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - Account | Account1, - Field( - description='Account to manage audiences for.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - audiences: Annotated[ - list[Audience] | None, - Field( - description='Audiences to sync (create or update). When omitted, the call is discovery-only and returns all existing audiences on the account without modification.', - min_length=1, - ), - ] = None - delete_missing: Annotated[ - bool | None, - Field( - description='When true, buyer-managed audiences on the account not included in this sync will be removed. Does not affect seller-managed audiences. Do not combine with an omitted audiences array or all buyer-managed audiences will be deleted.' - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_response.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_response.py deleted file mode 100644 index 59f6e226..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_audiences_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_audiences_response.json -# timestamp: 2026-06-18T11:31:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SyncAudiencesResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_request.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_request.py deleted file mode 100644 index 3f9cea42..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_request.py +++ /dev/null @@ -1,1005 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_catalogs_request.json -# timestamp: 2026-06-18T11:31:18+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Type(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class ConversionEvent(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIdType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: Annotated[ - Type, - Field( - description="Catalog type. Structural types: 'offering' (AdCP Offering objects), 'product' (ecommerce entries), 'inventory' (stock per location), 'store' (physical locations), 'promotion' (deals and pricing). Vertical types: 'hotel', 'flight', 'job', 'vehicle', 'real_estate', 'education', 'destination', 'app' — each with an industry-specific item schema.", - title='Catalog Type', - ), - ] - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: Annotated[ - FeedFormat | None, - Field( - description='Format of the external feed at url. Required when url points to a non-AdCP feed (e.g., Google Merchant Center XML, Meta Product Catalog). Omit for offering-type catalogs where the feed is native AdCP JSON.', - title='Feed Format', - ), - ] = None - update_frequency: Annotated[ - UpdateFrequency | None, - Field( - description='How often the platform should re-fetch the feed from url. Only applicable when url is provided. Platforms may use this as a hint for polling schedules.', - title='Update Frequency', - ), - ] = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[ConversionEvent] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: Annotated[ - ContentIdType | None, - Field( - description="Identifier type that the event's content_ids field should be matched against for items in this catalog. For example, 'gtin' means content_ids values are Global Trade Item Numbers, 'sku' means retailer SKUs. Omit when using a custom identifier scheme not listed in the enum.", - title='Content ID Type', - ), - ] = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class ValidationMode(StrEnum): - strict = 'strict' - lenient = 'lenient' - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SyncCatalogsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `catalog_id` gives resource-level dedup per catalog, but the sync envelope emits audit events and triggers platform review for large feeds — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `catalogs` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - Account | Account1, - Field( - description='Account that owns these catalogs.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - catalogs: Annotated[ - list[Catalog] | None, - Field( - description='Array of catalog feeds to sync (create or update). When omitted, the call is discovery-only and returns all existing catalogs on the account without modification.', - max_length=50, - min_length=1, - ), - ] = None - catalog_ids: Annotated[ - list[str] | None, - Field( - description='Optional filter to limit sync scope to specific catalog IDs. When provided, only these catalogs will be created/updated. Other catalogs on the account are unaffected.', - max_length=50, - min_length=1, - ), - ] = None - delete_missing: Annotated[ - bool | None, - Field( - description='When true, buyer-managed catalogs on the account not included in this sync will be removed. Does not affect seller-managed catalogs. Do not combine with an omitted catalogs array or all buyer-managed catalogs will be deleted.' - ), - ] = False - dry_run: Annotated[ - bool | None, - Field( - description='When true, preview changes without applying them. Returns what would be created/updated/deleted.' - ), - ] = False - validation_mode: Annotated[ - ValidationMode | None, - Field( - description="Validation strictness. 'strict' fails entire sync on any validation error. 'lenient' processes valid catalogs and reports errors.", - title='Validation Mode', - ), - ] = ValidationMode.strict - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async sync notifications. Publisher will send webhook when sync completes if operation takes longer than immediate response time (common for large feeds requiring platform review).', - title='Push Notification Config', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_response.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_response.py deleted file mode 100644 index 582f7db7..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_catalogs_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_catalogs_response.json -# timestamp: 2026-06-18T11:31:20+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SyncCatalogsResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_request.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_request.py deleted file mode 100644 index fec3dce2..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_request.py +++ /dev/null @@ -1,774 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_event_sources_request.json -# timestamp: 2026-06-01T00:35:34+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class Category(StrEnum): - owned_property = 'owned_property' - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class Surface(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - category: Annotated[ - Category, - Field( - description='Generic surface category. `owned_property` covers durable creator or brand-controlled properties hosted by a platform, such as channels, profiles, feeds, lists, podcasts, or playlists.' - ), - ] - property_type: Annotated[ - str | None, - Field( - description='Open vocabulary describing the kind of property, for example `channel`, `profile`, `feed`, `list`, `podcast`, `playlist`, or `newsletter`. Required by convention when `category` is `owned_property`; optional for other categories.', - max_length=128, - min_length=1, - ), - ] = None - namespace: Annotated[ - str | None, - Field( - description='Platform, publisher, or system namespace for the property, such as `video_platform`, `short_video_app`, `audio_service`, or a seller-defined namespace. This is intentionally not an enum.', - max_length=128, - min_length=1, - ), - ] = None - property_id: Annotated[ - str | None, - Field( - description='Optional identifier for the property within `namespace`.', - max_length=256, - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[str, Field(description='Unique identifier for this event source')] - name: Annotated[str | None, Field(description='Human-readable name for this event source')] = ( - None - ) - event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this source handles (e.g. purchase, lead). If omitted, accepts all event types.', - min_length=1, - ), - ] = None - action_source: Annotated[ - ActionSource | None, - Field( - description='Flat action-source category for this event source, such as website, app, in_store, system_generated, or other. Producers SHOULD also populate `surface` when the flat value is too coarse, and SHOULD keep this field aligned with `surface.category` when the category is also an ActionSource value.', - title='Action Source', - ), - ] = None - allowed_domains: Annotated[ - list[str] | None, - Field(description='Domains authorized to send events for this event source', min_length=1), - ] = None - surface: Annotated[ - Surface | None, - Field( - description='Optional structured surface this source represents, such as an owned channel, profile, feed, podcast, newsletter list, website, app, or store. Complements the flat `action_source` returned by sellers and avoids putting optimization-relevant surface meaning only in `ext`.', - title='Event Surface', - ), - ] = None - - -class SyncEventSourcesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. `event_source_id` gives resource-level dedup per source, but the sync envelope emits audit events and can trigger downstream pixel provisioning — this key prevents those side effects from firing twice on retry. Also serves as a request ID on discovery-only calls (when `event_sources` is omitted). MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - account: Annotated[ - Account | Account1, - Field( - description='Account to configure event sources for.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - event_sources: Annotated[ - list[EventSource] | None, - Field( - description='Event sources to sync (create or update). When omitted, the call is discovery-only and returns all existing event sources on the account without modification.', - min_length=1, - ), - ] = None - delete_missing: Annotated[ - bool | None, - Field(description='When true, event sources not included in this sync will be removed'), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_response.py b/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_response.py deleted file mode 100644 index bdd6e557..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/sync_event_sources_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: sync_event_sources_response.json -# timestamp: 2026-06-18T11:31:23+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SyncEventSourcesResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_request.py b/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_request.py deleted file mode 100644 index 47143b1e..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_request.py +++ /dev/null @@ -1,45763 +0,0 @@ -# generated by datamodel-codegen: -# filename: update_media_buy_request.json -# timestamp: 2026-06-18T11:31:42+00:00 - -from __future__ import annotations - -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Gtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)] - - -class Target3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - float, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class PostClick(Window): - pass - - -class PostView(Window): - pass - - -class DeclaredBy1(DeclaredBy): - pass - - -class VerifyAgent2(VerifyAgent): - pass - - -class VerifyAgent3(VerifyAgent1): - pass - - -class VerificationItem1(VerificationItem): - pass - - -class Target5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class GeoCountry(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class GeoCountriesExcludeItem(GeoCountry): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas1(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas2(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas3(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas4(AdCPBaseModel): - country: Annotated[Country, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas5(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas6(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas7(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas8(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas9(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas10(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude1(GeoPostalAreas1): - pass - - -class GeoPostalAreasExclude2(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude3(GeoPostalAreas3): - pass - - -class GeoPostalAreasExclude4(GeoPostalAreas4): - pass - - -class GeoPostalAreasExclude5(GeoPostalAreas5): - pass - - -class GeoPostalAreasExclude6(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude7(GeoPostalAreas7): - pass - - -class GeoPostalAreasExclude8(GeoPostalAreas8): - pass - - -class GeoPostalAreasExclude9(GeoPostalAreas9): - pass - - -class GeoPostalAreasExclude10(GeoPostalAreas10): - pass - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Signal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Signal4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal5(Signal1, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal6(Signal2, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal7(Signal3, Signal4): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal5 | Signal6 | Signal7]): - root: Annotated[ - Signal5 | Signal6 | Signal7, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalTargeting1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting(RootModel[SignalTargeting1 | SignalTargeting2 | SignalTargeting3]): - root: Annotated[ - SignalTargeting1 | SignalTargeting2 | SignalTargeting3, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress(Window): - pass - - -class Window1(Window): - pass - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Type(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight for this creative (0–100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description="Optional array of structured placement references where this creative should run within the already-purchased package inventory. New senders SHOULD use this field for placement-level creative routing because placement IDs are publisher-scoped. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. References entries from the product's `placements[]` array by `{ publisher_domain, placement_id }`; if `publisher_domain` is omitted in the ref, receivers MAY interpret it relative to the seller agent's own publisher domain in legacy single-publisher contexts. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description="Legacy shorthand array of placement IDs where this creative should run within the already-purchased package inventory. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. Receivers MAY interpret string IDs relative to the seller agent's own publisher domain in legacy single-publisher contexts. If `placement_refs` is also present, receivers MUST ignore this field.", - min_length=1, - ), - ] = None - - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class FormatOptionRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRef1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class DeclaredBy2(DeclaredBy): - pass - - -class VerifyAgent4(VerifyAgent): - pass - - -class VerifyAgent5(VerifyAgent1): - pass - - -class VerificationItem2(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy3(DeclaredBy): - pass - - -class VerifyAgent6(VerifyAgent): - pass - - -class VerifyAgent7(VerifyAgent1): - pass - - -class VerificationItem3(VerificationItem): - pass - - -class DeclaredBy4(DeclaredBy): - pass - - -class VerifyAgent8(VerifyAgent): - pass - - -class VerifyAgent9(VerifyAgent1): - pass - - -class VerificationItem4(VerificationItem): - pass - - -class DeclaredBy5(DeclaredBy): - pass - - -class VerifyAgent10(VerifyAgent): - pass - - -class VerifyAgent11(VerifyAgent1): - pass - - -class VerificationItem5(VerificationItem): - pass - - -class DeclaredBy6(DeclaredBy): - pass - - -class VerifyAgent12(VerifyAgent): - pass - - -class VerifyAgent13(VerifyAgent1): - pass - - -class VerificationItem6(VerificationItem): - pass - - -class DeclaredBy7(DeclaredBy): - pass - - -class VerifyAgent14(VerifyAgent): - pass - - -class VerifyAgent15(VerifyAgent1): - pass - - -class VerificationItem7(VerificationItem): - pass - - -class DeclaredBy8(DeclaredBy): - pass - - -class VerifyAgent16(VerifyAgent): - pass - - -class VerifyAgent17(VerifyAgent1): - pass - - -class VerificationItem8(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy9(DeclaredBy): - pass - - -class VerifyAgent18(VerifyAgent): - pass - - -class VerifyAgent19(VerifyAgent1): - pass - - -class VerificationItem9(VerificationItem): - pass - - -class DeclaredBy10(DeclaredBy): - pass - - -class VerifyAgent20(VerifyAgent): - pass - - -class VerifyAgent21(VerifyAgent1): - pass - - -class VerificationItem10(VerificationItem): - pass - - -class DeclaredBy11(DeclaredBy): - pass - - -class VerifyAgent22(VerifyAgent): - pass - - -class VerifyAgent23(VerifyAgent1): - pass - - -class VerificationItem11(VerificationItem): - pass - - -class DeclaredBy12(DeclaredBy): - pass - - -class VerifyAgent24(VerifyAgent): - pass - - -class VerifyAgent25(VerifyAgent1): - pass - - -class VerificationItem12(VerificationItem): - pass - - -class DeclaredBy13(DeclaredBy): - pass - - -class VerifyAgent26(VerifyAgent): - pass - - -class VerifyAgent27(VerifyAgent1): - pass - - -class VerificationItem13(VerificationItem): - pass - - -class DeclaredBy14(DeclaredBy): - pass - - -class VerifyAgent28(VerifyAgent): - pass - - -class VerifyAgent29(VerifyAgent1): - pass - - -class VerificationItem14(VerificationItem): - pass - - -class DeclaredBy15(DeclaredBy): - pass - - -class VerifyAgent30(VerifyAgent): - pass - - -class VerifyAgent31(VerifyAgent1): - pass - - -class VerificationItem15(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role16(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role16, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction16(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class FeedFieldMapping1(FeedFieldMapping): - pass - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy16(DeclaredBy): - pass - - -class VerifyAgent32(VerifyAgent): - pass - - -class VerifyAgent33(VerifyAgent1): - pass - - -class VerificationItem16(VerificationItem): - pass - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent1): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class DeclaredBy18(DeclaredBy): - pass - - -class VerifyAgent36(VerifyAgent): - pass - - -class VerifyAgent37(VerifyAgent1): - pass - - -class VerificationItem18(VerificationItem): - pass - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent1): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class PlatformExtension(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class DeclaredBy20(DeclaredBy): - pass - - -class VerifyAgent40(VerifyAgent): - pass - - -class VerifyAgent41(VerifyAgent1): - pass - - -class VerificationItem20(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy21(DeclaredBy): - pass - - -class VerifyAgent42(VerifyAgent): - pass - - -class VerifyAgent43(VerifyAgent1): - pass - - -class VerificationItem21(VerificationItem): - pass - - -class Target7(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy22(DeclaredBy): - pass - - -class VerifyAgent44(VerifyAgent): - pass - - -class VerifyAgent45(VerifyAgent1): - pass - - -class VerificationItem22(VerificationItem): - pass - - -class Target8(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy23(DeclaredBy): - pass - - -class VerifyAgent46(VerifyAgent): - pass - - -class VerifyAgent47(VerifyAgent1): - pass - - -class VerificationItem23(VerificationItem): - pass - - -class DeclaredBy24(DeclaredBy): - pass - - -class VerifyAgent48(VerifyAgent): - pass - - -class VerifyAgent49(VerifyAgent1): - pass - - -class VerificationItem24(VerificationItem): - pass - - -class DeclaredBy25(DeclaredBy): - pass - - -class VerifyAgent50(VerifyAgent): - pass - - -class VerifyAgent51(VerifyAgent1): - pass - - -class VerificationItem25(VerificationItem): - pass - - -class DeclaredBy26(DeclaredBy): - pass - - -class VerifyAgent52(VerifyAgent): - pass - - -class VerifyAgent53(VerifyAgent1): - pass - - -class VerificationItem26(VerificationItem): - pass - - -class DeclaredBy27(DeclaredBy): - pass - - -class VerifyAgent54(VerifyAgent): - pass - - -class VerifyAgent55(VerifyAgent1): - pass - - -class VerificationItem27(VerificationItem): - pass - - -class DeclaredBy28(DeclaredBy): - pass - - -class VerifyAgent56(VerifyAgent): - pass - - -class VerifyAgent57(VerifyAgent1): - pass - - -class VerificationItem28(VerificationItem): - pass - - -class DeclaredBy29(DeclaredBy): - pass - - -class VerifyAgent58(VerifyAgent): - pass - - -class VerifyAgent59(VerifyAgent1): - pass - - -class VerificationItem29(VerificationItem): - pass - - -class DeclaredBy30(DeclaredBy): - pass - - -class VerifyAgent60(VerifyAgent): - pass - - -class VerifyAgent61(VerifyAgent1): - pass - - -class VerificationItem30(VerificationItem): - pass - - -class DeclaredBy31(DeclaredBy): - pass - - -class VerifyAgent62(VerifyAgent): - pass - - -class VerifyAgent63(VerifyAgent1): - pass - - -class VerificationItem31(VerificationItem): - pass - - -class DeclaredBy32(DeclaredBy): - pass - - -class VerifyAgent64(VerifyAgent): - pass - - -class VerifyAgent65(VerifyAgent1): - pass - - -class VerificationItem32(VerificationItem): - pass - - -class DeclaredBy33(DeclaredBy): - pass - - -class VerifyAgent66(VerifyAgent): - pass - - -class VerifyAgent67(VerifyAgent1): - pass - - -class VerificationItem33(VerificationItem): - pass - - -class DeclaredBy34(DeclaredBy): - pass - - -class VerifyAgent68(VerifyAgent): - pass - - -class VerifyAgent69(VerifyAgent1): - pass - - -class VerificationItem34(VerificationItem): - pass - - -class DeclaredBy35(DeclaredBy): - pass - - -class VerifyAgent70(VerifyAgent): - pass - - -class VerifyAgent71(VerifyAgent1): - pass - - -class VerificationItem35(VerificationItem): - pass - - -class DeclaredBy36(DeclaredBy): - pass - - -class VerifyAgent72(VerifyAgent): - pass - - -class VerifyAgent73(VerifyAgent1): - pass - - -class VerificationItem36(VerificationItem): - pass - - -class DeclaredBy37(DeclaredBy): - pass - - -class VerifyAgent74(VerifyAgent): - pass - - -class VerifyAgent75(VerifyAgent1): - pass - - -class VerificationItem37(VerificationItem): - pass - - -class ReferenceAsset1(ReferenceAsset): - pass - - -class FeedFieldMapping2(FeedFieldMapping): - pass - - -class ReferenceAuthorization1(ReferenceAuthorization): - pass - - -class DeclaredBy38(DeclaredBy): - pass - - -class VerifyAgent76(VerifyAgent): - pass - - -class VerifyAgent77(VerifyAgent1): - pass - - -class VerificationItem38(VerificationItem): - pass - - -class DeclaredBy39(DeclaredBy): - pass - - -class VerifyAgent78(VerifyAgent): - pass - - -class VerifyAgent79(VerifyAgent1): - pass - - -class VerificationItem39(VerificationItem): - pass - - -class DeclaredBy40(DeclaredBy): - pass - - -class VerifyAgent80(VerifyAgent): - pass - - -class VerifyAgent81(VerifyAgent1): - pass - - -class VerificationItem40(VerificationItem): - pass - - -class DeclaredBy41(DeclaredBy): - pass - - -class VerifyAgent82(VerifyAgent): - pass - - -class VerifyAgent83(VerifyAgent1): - pass - - -class VerificationItem41(VerificationItem): - pass - - -class DeclaredBy42(DeclaredBy): - pass - - -class VerifyAgent84(VerifyAgent): - pass - - -class VerifyAgent85(VerifyAgent1): - pass - - -class VerificationItem42(VerificationItem): - pass - - -class DeclaredBy43(DeclaredBy): - pass - - -class VerifyAgent86(VerifyAgent): - pass - - -class VerifyAgent87(VerifyAgent1): - pass - - -class VerificationItem43(VerificationItem): - pass - - -class DeclaredBy44(DeclaredBy): - pass - - -class VerifyAgent88(VerifyAgent): - pass - - -class VerifyAgent89(VerifyAgent1): - pass - - -class VerificationItem44(VerificationItem): - pass - - -class DeclaredBy45(DeclaredBy): - pass - - -class VerifyAgent90(VerifyAgent): - pass - - -class VerifyAgent91(VerifyAgent1): - pass - - -class VerificationItem45(VerificationItem): - pass - - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values to apply for this preview') - ] = None - context_description: Annotated[ - str | None, - Field(description='Natural language description of the context for AI-generated content'), - ] = None - - -class DeclaredBy46(DeclaredBy): - pass - - -class VerifyAgent92(VerifyAgent): - pass - - -class VerifyAgent93(VerifyAgent1): - pass - - -class VerificationItem46(VerificationItem): - pass - - - -class DeclaredBy47(DeclaredBy): - pass - - -class VerifyAgent94(VerifyAgent): - pass - - -class VerifyAgent95(VerifyAgent1): - pass - - -class VerificationItem47(VerificationItem): - pass - - -class DeclaredBy48(DeclaredBy): - pass - - -class VerifyAgent96(VerifyAgent): - pass - - -class VerifyAgent97(VerifyAgent1): - pass - - -class VerificationItem48(VerificationItem): - pass - - -class DeclaredBy49(DeclaredBy): - pass - - -class VerifyAgent98(VerifyAgent): - pass - - -class VerifyAgent99(VerifyAgent1): - pass - - -class VerificationItem49(VerificationItem): - pass - - -class DeclaredBy50(DeclaredBy): - pass - - -class VerifyAgent100(VerifyAgent): - pass - - -class VerifyAgent101(VerifyAgent1): - pass - - -class VerificationItem50(VerificationItem): - pass - - -class DeclaredBy51(DeclaredBy): - pass - - -class VerifyAgent102(VerifyAgent): - pass - - -class VerifyAgent103(VerifyAgent1): - pass - - -class VerificationItem51(VerificationItem): - pass - - -class DeclaredBy52(DeclaredBy): - pass - - -class VerifyAgent104(VerifyAgent): - pass - - -class VerifyAgent105(VerifyAgent1): - pass - - -class VerificationItem52(VerificationItem): - pass - - -class DeclaredBy53(DeclaredBy): - pass - - -class VerifyAgent106(VerifyAgent): - pass - - -class VerifyAgent107(VerifyAgent1): - pass - - -class VerificationItem53(VerificationItem): - pass - - -class DeclaredBy54(DeclaredBy): - pass - - -class VerifyAgent108(VerifyAgent): - pass - - -class VerifyAgent109(VerifyAgent1): - pass - - -class VerificationItem54(VerificationItem): - pass - - -class DeclaredBy55(DeclaredBy): - pass - - -class VerifyAgent110(VerifyAgent): - pass - - -class VerifyAgent111(VerifyAgent1): - pass - - -class VerificationItem55(VerificationItem): - pass - - -class DeclaredBy56(DeclaredBy): - pass - - -class VerifyAgent112(VerifyAgent): - pass - - -class VerifyAgent113(VerifyAgent1): - pass - - -class VerificationItem56(VerificationItem): - pass - - -class DeclaredBy57(DeclaredBy): - pass - - -class VerifyAgent114(VerifyAgent): - pass - - -class VerifyAgent115(VerifyAgent1): - pass - - -class VerificationItem57(VerificationItem): - pass - - -class DeclaredBy58(DeclaredBy): - pass - - -class VerifyAgent116(VerifyAgent): - pass - - -class VerifyAgent117(VerifyAgent1): - pass - - -class VerificationItem58(VerificationItem): - pass - - -class DeclaredBy59(DeclaredBy): - pass - - -class VerifyAgent118(VerifyAgent): - pass - - -class VerifyAgent119(VerifyAgent1): - pass - - -class VerificationItem59(VerificationItem): - pass - - -class DeclaredBy60(DeclaredBy): - pass - - -class VerifyAgent120(VerifyAgent): - pass - - -class VerifyAgent121(VerifyAgent1): - pass - - -class VerificationItem60(VerificationItem): - pass - - -class ReferenceAsset2(ReferenceAsset): - pass - - -class FeedFieldMapping3(FeedFieldMapping): - pass - - -class ReferenceAuthorization2(ReferenceAuthorization): - pass - - -class DeclaredBy61(DeclaredBy): - pass - - -class VerifyAgent122(VerifyAgent): - pass - - -class VerifyAgent123(VerifyAgent1): - pass - - -class VerificationItem61(VerificationItem): - pass - - -class DeclaredBy62(DeclaredBy): - pass - - -class VerifyAgent124(VerifyAgent): - pass - - -class VerifyAgent125(VerifyAgent1): - pass - - -class VerificationItem62(VerificationItem): - pass - - -class DeclaredBy63(DeclaredBy): - pass - - -class VerifyAgent126(VerifyAgent): - pass - - -class VerifyAgent127(VerifyAgent1): - pass - - -class VerificationItem63(VerificationItem): - pass - - -class DeclaredBy64(DeclaredBy): - pass - - -class VerifyAgent128(VerifyAgent): - pass - - -class VerifyAgent129(VerifyAgent1): - pass - - -class VerificationItem64(VerificationItem): - pass - - -class DeclaredBy65(DeclaredBy): - pass - - -class VerifyAgent130(VerifyAgent): - pass - - -class VerifyAgent131(VerifyAgent1): - pass - - -class VerificationItem65(VerificationItem): - pass - - -class DeclaredBy66(DeclaredBy): - pass - - -class VerifyAgent132(VerifyAgent): - pass - - -class VerifyAgent133(VerifyAgent1): - pass - - -class VerificationItem66(VerificationItem): - pass - - -class DeclaredBy67(DeclaredBy): - pass - - -class VerifyAgent134(VerifyAgent): - pass - - -class VerifyAgent135(VerifyAgent1): - pass - - -class VerificationItem67(VerificationItem): - pass - - -class DeclaredBy68(DeclaredBy): - pass - - -class VerifyAgent136(VerifyAgent): - pass - - -class VerifyAgent137(VerifyAgent1): - pass - - -class VerificationItem68(VerificationItem): - pass - - -class DeclaredBy69(DeclaredBy): - pass - - -class VerifyAgent138(VerifyAgent): - pass - - -class VerifyAgent139(VerifyAgent1): - pass - - -class VerificationItem69(VerificationItem): - pass - - -class DeclaredBy70(DeclaredBy): - pass - - -class VerifyAgent140(VerifyAgent): - pass - - -class VerifyAgent141(VerifyAgent1): - pass - - -class VerificationItem70(VerificationItem): - pass - - -class DeclaredBy71(DeclaredBy): - pass - - -class VerifyAgent142(VerifyAgent): - pass - - -class VerifyAgent143(VerifyAgent1): - pass - - -class VerificationItem71(VerificationItem): - pass - - -class DeclaredBy72(DeclaredBy): - pass - - -class VerifyAgent144(VerifyAgent): - pass - - -class VerifyAgent145(VerifyAgent1): - pass - - -class VerificationItem72(VerificationItem): - pass - - -class DeclaredBy73(DeclaredBy): - pass - - -class VerifyAgent146(VerifyAgent): - pass - - -class VerifyAgent147(VerifyAgent1): - pass - - -class VerificationItem73(VerificationItem): - pass - - -class DeclaredBy74(DeclaredBy): - pass - - -class VerifyAgent148(VerifyAgent): - pass - - -class VerifyAgent149(VerifyAgent1): - pass - - -class VerificationItem74(VerificationItem): - pass - - -class DeclaredBy75(DeclaredBy): - pass - - -class VerifyAgent150(VerifyAgent): - pass - - -class VerifyAgent151(VerifyAgent1): - pass - - -class VerificationItem75(VerificationItem): - pass - - -class DeclaredBy76(DeclaredBy): - pass - - -class VerifyAgent152(VerifyAgent): - pass - - -class VerifyAgent153(VerifyAgent1): - pass - - -class VerificationItem76(VerificationItem): - pass - - -class DeclaredBy77(DeclaredBy): - pass - - -class VerifyAgent154(VerifyAgent): - pass - - -class VerifyAgent155(VerifyAgent1): - pass - - -class VerificationItem77(VerificationItem): - pass - - -class DeclaredBy78(DeclaredBy): - pass - - -class VerifyAgent156(VerifyAgent): - pass - - -class VerifyAgent157(VerifyAgent1): - pass - - -class VerificationItem78(VerificationItem): - pass - - -class DeclaredBy79(DeclaredBy): - pass - - -class VerifyAgent158(VerifyAgent): - pass - - -class VerifyAgent159(VerifyAgent1): - pass - - -class VerificationItem79(VerificationItem): - pass - - -class DeclaredBy80(DeclaredBy): - pass - - -class VerifyAgent160(VerifyAgent): - pass - - -class VerifyAgent161(VerifyAgent1): - pass - - -class VerificationItem80(VerificationItem): - pass - - -class DeclaredBy81(DeclaredBy): - pass - - -class VerifyAgent162(VerifyAgent): - pass - - -class VerifyAgent163(VerifyAgent1): - pass - - -class VerificationItem81(VerificationItem): - pass - - -class DeclaredBy82(DeclaredBy): - pass - - -class VerifyAgent164(VerifyAgent): - pass - - -class VerifyAgent165(VerifyAgent1): - pass - - -class VerificationItem82(VerificationItem): - pass - - -class ReferenceAsset3(ReferenceAsset): - pass - - -class FeedFieldMapping4(FeedFieldMapping): - pass - - -class ReferenceAuthorization3(ReferenceAuthorization): - pass - - -class DeclaredBy83(DeclaredBy): - pass - - -class VerifyAgent166(VerifyAgent): - pass - - -class VerifyAgent167(VerifyAgent1): - pass - - -class VerificationItem83(VerificationItem): - pass - - -class DeclaredBy84(DeclaredBy): - pass - - -class VerifyAgent168(VerifyAgent): - pass - - -class VerifyAgent169(VerifyAgent1): - pass - - -class VerificationItem84(VerificationItem): - pass - - -class DeclaredBy85(DeclaredBy): - pass - - -class VerifyAgent170(VerifyAgent): - pass - - -class VerifyAgent171(VerifyAgent1): - pass - - -class VerificationItem85(VerificationItem): - pass - - -class DeclaredBy86(DeclaredBy): - pass - - -class VerifyAgent172(VerifyAgent): - pass - - -class VerifyAgent173(VerifyAgent1): - pass - - -class VerificationItem86(VerificationItem): - pass - - -class DeclaredBy87(DeclaredBy): - pass - - -class VerifyAgent174(VerifyAgent): - pass - - -class VerifyAgent175(VerifyAgent1): - pass - - -class VerificationItem87(VerificationItem): - pass - - -class DeclaredBy88(DeclaredBy): - pass - - -class VerifyAgent176(VerifyAgent): - pass - - -class VerifyAgent177(VerifyAgent1): - pass - - -class VerificationItem88(VerificationItem): - pass - - -class DeclaredBy89(DeclaredBy): - pass - - -class VerifyAgent178(VerifyAgent): - pass - - -class VerifyAgent179(VerifyAgent1): - pass - - -class VerificationItem89(VerificationItem): - pass - - -class DeclaredBy90(DeclaredBy): - pass - - -class VerifyAgent180(VerifyAgent): - pass - - -class VerifyAgent181(VerifyAgent1): - pass - - -class VerificationItem90(VerificationItem): - pass - - -class DeclaredBy91(DeclaredBy): - pass - - -class VerifyAgent182(VerifyAgent): - pass - - -class VerifyAgent183(VerifyAgent1): - pass - - -class VerificationItem91(VerificationItem): - pass - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role96(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role96, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class InvoiceRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - - -class FormatOptionRefs(RootModel[FormatOptionRef | FormatOptionRef1]): - root: Annotated[ - FormatOptionRef | FormatOptionRef1, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FeedFieldMapping5(FeedFieldMapping): - pass - - -class Window2(Window): - pass - - -class TargetFrequency1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window2, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - - - - - -class PostClick1(Window): - pass - - -class PostView1(Window): - pass - - -class DeclaredBy92(DeclaredBy): - pass - - -class VerifyAgent184(VerifyAgent): - pass - - -class VerifyAgent185(VerifyAgent1): - pass - - -class VerificationItem92(VerificationItem): - pass - - - -class GeoPostalAreas231(GeoPostalAreas1): - pass - - -class GeoPostalAreas232(GeoPostalAreas2): - pass - - -class GeoPostalAreas233(GeoPostalAreas3): - pass - - -class GeoPostalAreas234(GeoPostalAreas4): - pass - - -class GeoPostalAreas235(GeoPostalAreas5): - pass - - -class GeoPostalAreas236(GeoPostalAreas6): - pass - - -class GeoPostalAreas237(GeoPostalAreas7): - pass - - -class GeoPostalAreas238(GeoPostalAreas8): - pass - - -class GeoPostalAreas239(GeoPostalAreas9): - pass - - -class GeoPostalAreas2310(GeoPostalAreas10): - pass - - -class GeoPostalAreasExclude231(GeoPostalAreas1): - pass - - -class GeoPostalAreasExclude232(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude233(GeoPostalAreas3): - pass - - -class GeoPostalAreasExclude234(GeoPostalAreas4): - pass - - -class GeoPostalAreasExclude235(GeoPostalAreas5): - pass - - -class GeoPostalAreasExclude236(GeoPostalAreas6): - pass - - -class GeoPostalAreasExclude237(GeoPostalAreas7): - pass - - -class GeoPostalAreasExclude238(GeoPostalAreas8): - pass - - -class GeoPostalAreasExclude239(GeoPostalAreas9): - pass - - -class GeoPostalAreasExclude2310(GeoPostalAreas10): - pass - - - - -class Signal81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - - -class Signal84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal85(Signal81, Signal84): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal86(Signal82, Signal84): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal87(Signal83, Signal84): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey1 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal8(RootModel[Signal85 | Signal86 | Signal87]): - root: Annotated[ - Signal85 | Signal86 | Signal87, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal8], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group1], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - - - -class SignalTargeting5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef1 | SignalRef2 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId1 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting4(RootModel[SignalTargeting5 | SignalTargeting6 | SignalTargeting7]): - root: Annotated[ - SignalTargeting5 | SignalTargeting6 | SignalTargeting7, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress1(Window): - pass - - -class Window3(Window): - pass - - -class Geometry1(Geometry): - pass - - -class DeclaredBy93(DeclaredBy): - pass - - -class VerifyAgent186(VerifyAgent): - pass - - -class VerifyAgent187(VerifyAgent1): - pass - - -class VerificationItem93(VerificationItem): - pass - - -class AvailableRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[AvailableRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class Metric2(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class DeclaredBy94(DeclaredBy): - pass - - -class VerifyAgent188(VerifyAgent): - pass - - -class VerifyAgent189(VerifyAgent1): - pass - - -class VerificationItem94(VerificationItem): - pass - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class AttributionWindow2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class DeclaredBy95(DeclaredBy): - pass - - -class VerifyAgent190(VerifyAgent): - pass - - -class VerifyAgent191(VerifyAgent1): - pass - - -class VerificationItem95(VerificationItem): - pass - - -class CreativeAssignment1(CreativeAssignment): - pass - - - -class DeclaredBy96(DeclaredBy): - pass - - -class VerifyAgent192(VerifyAgent): - pass - - -class VerifyAgent193(VerifyAgent1): - pass - - -class VerificationItem96(VerificationItem): - pass - - -class DeclaredBy97(DeclaredBy): - pass - - -class VerifyAgent194(VerifyAgent): - pass - - -class VerifyAgent195(VerifyAgent1): - pass - - -class VerificationItem97(VerificationItem): - pass - - -class DeclaredBy98(DeclaredBy): - pass - - -class VerifyAgent196(VerifyAgent): - pass - - -class VerifyAgent197(VerifyAgent1): - pass - - -class VerificationItem98(VerificationItem): - pass - - -class DeclaredBy99(DeclaredBy): - pass - - -class VerifyAgent198(VerifyAgent): - pass - - -class VerifyAgent199(VerifyAgent1): - pass - - -class VerificationItem99(VerificationItem): - pass - - -class DeclaredBy100(DeclaredBy): - pass - - -class VerifyAgent200(VerifyAgent): - pass - - -class VerifyAgent201(VerifyAgent1): - pass - - -class VerificationItem100(VerificationItem): - pass - - -class DeclaredBy101(DeclaredBy): - pass - - -class VerifyAgent202(VerifyAgent): - pass - - -class VerifyAgent203(VerifyAgent1): - pass - - -class VerificationItem101(VerificationItem): - pass - - -class DeclaredBy102(DeclaredBy): - pass - - -class VerifyAgent204(VerifyAgent): - pass - - -class VerifyAgent205(VerifyAgent1): - pass - - -class VerificationItem102(VerificationItem): - pass - - -class DeclaredBy103(DeclaredBy): - pass - - -class VerifyAgent206(VerifyAgent): - pass - - -class VerifyAgent207(VerifyAgent1): - pass - - -class VerificationItem103(VerificationItem): - pass - - -class DeclaredBy104(DeclaredBy): - pass - - -class VerifyAgent208(VerifyAgent): - pass - - -class VerifyAgent209(VerifyAgent1): - pass - - -class VerificationItem104(VerificationItem): - pass - - -class DeclaredBy105(DeclaredBy): - pass - - -class VerifyAgent210(VerifyAgent): - pass - - -class VerifyAgent211(VerifyAgent1): - pass - - -class VerificationItem105(VerificationItem): - pass - - -class DeclaredBy106(DeclaredBy): - pass - - -class VerifyAgent212(VerifyAgent): - pass - - -class VerifyAgent213(VerifyAgent1): - pass - - -class VerificationItem106(VerificationItem): - pass - - -class DeclaredBy107(DeclaredBy): - pass - - -class VerifyAgent214(VerifyAgent): - pass - - -class VerifyAgent215(VerifyAgent1): - pass - - -class VerificationItem107(VerificationItem): - pass - - -class DeclaredBy108(DeclaredBy): - pass - - -class VerifyAgent216(VerifyAgent): - pass - - -class VerifyAgent217(VerifyAgent1): - pass - - -class VerificationItem108(VerificationItem): - pass - - -class DeclaredBy109(DeclaredBy): - pass - - -class VerifyAgent218(VerifyAgent): - pass - - -class VerifyAgent219(VerifyAgent1): - pass - - -class VerificationItem109(VerificationItem): - pass - - -class ReferenceAsset4(ReferenceAsset): - pass - - -class FeedFieldMapping6(FeedFieldMapping): - pass - - -class ReferenceAuthorization4(ReferenceAuthorization): - pass - - -class DeclaredBy110(DeclaredBy): - pass - - -class VerifyAgent220(VerifyAgent): - pass - - -class VerifyAgent221(VerifyAgent1): - pass - - -class VerificationItem110(VerificationItem): - pass - - -class DeclaredBy111(DeclaredBy): - pass - - -class VerifyAgent222(VerifyAgent): - pass - - -class VerifyAgent223(VerifyAgent1): - pass - - -class VerificationItem111(VerificationItem): - pass - - -class DeclaredBy112(DeclaredBy): - pass - - -class VerifyAgent224(VerifyAgent): - pass - - -class VerifyAgent225(VerifyAgent1): - pass - - -class VerificationItem112(VerificationItem): - pass - - -class DeclaredBy113(DeclaredBy): - pass - - -class VerifyAgent226(VerifyAgent): - pass - - -class VerifyAgent227(VerifyAgent1): - pass - - -class VerificationItem113(VerificationItem): - pass - - -class DeclaredBy114(DeclaredBy): - pass - - -class VerifyAgent228(VerifyAgent): - pass - - -class VerifyAgent229(VerifyAgent1): - pass - - -class VerificationItem114(VerificationItem): - pass - - -class DeclaredBy115(DeclaredBy): - pass - - -class VerifyAgent230(VerifyAgent): - pass - - -class VerifyAgent231(VerifyAgent1): - pass - - -class VerificationItem115(VerificationItem): - pass - - -class DeclaredBy116(DeclaredBy): - pass - - -class VerifyAgent232(VerifyAgent): - pass - - -class VerifyAgent233(VerifyAgent1): - pass - - -class VerificationItem116(VerificationItem): - pass - - -class DeclaredBy117(DeclaredBy): - pass - - -class VerifyAgent234(VerifyAgent): - pass - - -class VerifyAgent235(VerifyAgent1): - pass - - -class VerificationItem117(VerificationItem): - pass - - -class DeclaredBy118(DeclaredBy): - pass - - -class VerifyAgent236(VerifyAgent): - pass - - -class VerifyAgent237(VerifyAgent1): - pass - - -class VerificationItem118(VerificationItem): - pass - - -class DeclaredBy119(DeclaredBy): - pass - - -class VerifyAgent238(VerifyAgent): - pass - - -class VerifyAgent239(VerifyAgent1): - pass - - -class VerificationItem119(VerificationItem): - pass - - -class DeclaredBy120(DeclaredBy): - pass - - -class VerifyAgent240(VerifyAgent): - pass - - -class VerifyAgent241(VerifyAgent1): - pass - - -class VerificationItem120(VerificationItem): - pass - - -class DeclaredBy121(DeclaredBy): - pass - - -class VerifyAgent242(VerifyAgent): - pass - - -class VerifyAgent243(VerifyAgent1): - pass - - -class VerificationItem121(VerificationItem): - pass - - -class DeclaredBy122(DeclaredBy): - pass - - -class VerifyAgent244(VerifyAgent): - pass - - -class VerifyAgent245(VerifyAgent1): - pass - - -class VerificationItem122(VerificationItem): - pass - - -class DeclaredBy123(DeclaredBy): - pass - - -class VerifyAgent246(VerifyAgent): - pass - - -class VerifyAgent247(VerifyAgent1): - pass - - -class VerificationItem123(VerificationItem): - pass - - -class DeclaredBy124(DeclaredBy): - pass - - -class VerifyAgent248(VerifyAgent): - pass - - -class VerifyAgent249(VerifyAgent1): - pass - - -class VerificationItem124(VerificationItem): - pass - - -class DeclaredBy125(DeclaredBy): - pass - - -class VerifyAgent250(VerifyAgent): - pass - - -class VerifyAgent251(VerifyAgent1): - pass - - -class VerificationItem125(VerificationItem): - pass - - -class DeclaredBy126(DeclaredBy): - pass - - -class VerifyAgent252(VerifyAgent): - pass - - -class VerifyAgent253(VerifyAgent1): - pass - - -class VerificationItem126(VerificationItem): - pass - - -class DeclaredBy127(DeclaredBy): - pass - - -class VerifyAgent254(VerifyAgent): - pass - - -class VerifyAgent255(VerifyAgent1): - pass - - -class VerificationItem127(VerificationItem): - pass - - -class DeclaredBy128(DeclaredBy): - pass - - -class VerifyAgent256(VerifyAgent): - pass - - -class VerifyAgent257(VerifyAgent1): - pass - - -class VerificationItem128(VerificationItem): - pass - - -class DeclaredBy129(DeclaredBy): - pass - - -class VerifyAgent258(VerifyAgent): - pass - - -class VerifyAgent259(VerifyAgent1): - pass - - -class VerificationItem129(VerificationItem): - pass - - -class DeclaredBy130(DeclaredBy): - pass - - -class VerifyAgent260(VerifyAgent): - pass - - -class VerifyAgent261(VerifyAgent1): - pass - - -class VerificationItem130(VerificationItem): - pass - - -class DeclaredBy131(DeclaredBy): - pass - - -class VerifyAgent262(VerifyAgent): - pass - - -class VerifyAgent263(VerifyAgent1): - pass - - -class VerificationItem131(VerificationItem): - pass - - -class ReferenceAsset5(ReferenceAsset): - pass - - -class FeedFieldMapping7(FeedFieldMapping): - pass - - -class ReferenceAuthorization5(ReferenceAuthorization): - pass - - -class DeclaredBy132(DeclaredBy): - pass - - -class VerifyAgent264(VerifyAgent): - pass - - -class VerifyAgent265(VerifyAgent1): - pass - - -class VerificationItem132(VerificationItem): - pass - - -class DeclaredBy133(DeclaredBy): - pass - - -class VerifyAgent266(VerifyAgent): - pass - - -class VerifyAgent267(VerifyAgent1): - pass - - -class VerificationItem133(VerificationItem): - pass - - -class DeclaredBy134(DeclaredBy): - pass - - -class VerifyAgent268(VerifyAgent): - pass - - -class VerifyAgent269(VerifyAgent1): - pass - - -class VerificationItem134(VerificationItem): - pass - - -class DeclaredBy135(DeclaredBy): - pass - - -class VerifyAgent270(VerifyAgent): - pass - - -class VerifyAgent271(VerifyAgent1): - pass - - -class VerificationItem135(VerificationItem): - pass - - -class DeclaredBy136(DeclaredBy): - pass - - -class VerifyAgent272(VerifyAgent): - pass - - -class VerifyAgent273(VerifyAgent1): - pass - - -class VerificationItem136(VerificationItem): - pass - - -class DeclaredBy137(DeclaredBy): - pass - - -class VerifyAgent274(VerifyAgent): - pass - - -class VerifyAgent275(VerifyAgent1): - pass - - -class VerificationItem137(VerificationItem): - pass - - -class DeclaredBy138(DeclaredBy): - pass - - -class VerifyAgent276(VerifyAgent): - pass - - -class VerifyAgent277(VerifyAgent1): - pass - - -class VerificationItem138(VerificationItem): - pass - - -class DeclaredBy139(DeclaredBy): - pass - - -class VerifyAgent278(VerifyAgent): - pass - - -class VerifyAgent279(VerifyAgent1): - pass - - -class VerificationItem139(VerificationItem): - pass - - -class DeclaredBy140(DeclaredBy): - pass - - -class VerifyAgent280(VerifyAgent): - pass - - -class VerifyAgent281(VerifyAgent1): - pass - - -class VerificationItem140(VerificationItem): - pass - - - -class DeclaredBy141(DeclaredBy): - pass - - -class VerifyAgent282(VerifyAgent): - pass - - -class VerifyAgent283(VerifyAgent1): - pass - - -class VerificationItem141(VerificationItem): - pass - - -class DeclaredBy142(DeclaredBy): - pass - - -class VerifyAgent284(VerifyAgent): - pass - - -class VerifyAgent285(VerifyAgent1): - pass - - -class VerificationItem142(VerificationItem): - pass - - -class DeclaredBy143(DeclaredBy): - pass - - -class VerifyAgent286(VerifyAgent): - pass - - -class VerifyAgent287(VerifyAgent1): - pass - - -class VerificationItem143(VerificationItem): - pass - - -class DeclaredBy144(DeclaredBy): - pass - - -class VerifyAgent288(VerifyAgent): - pass - - -class VerifyAgent289(VerifyAgent1): - pass - - -class VerificationItem144(VerificationItem): - pass - - -class DeclaredBy145(DeclaredBy): - pass - - -class VerifyAgent290(VerifyAgent): - pass - - -class VerifyAgent291(VerifyAgent1): - pass - - -class VerificationItem145(VerificationItem): - pass - - -class DeclaredBy146(DeclaredBy): - pass - - -class VerifyAgent292(VerifyAgent): - pass - - -class VerifyAgent293(VerifyAgent1): - pass - - -class VerificationItem146(VerificationItem): - pass - - -class DeclaredBy147(DeclaredBy): - pass - - -class VerifyAgent294(VerifyAgent): - pass - - -class VerifyAgent295(VerifyAgent1): - pass - - -class VerificationItem147(VerificationItem): - pass - - -class DeclaredBy148(DeclaredBy): - pass - - -class VerifyAgent296(VerifyAgent): - pass - - -class VerifyAgent297(VerifyAgent1): - pass - - -class VerificationItem148(VerificationItem): - pass - - -class DeclaredBy149(DeclaredBy): - pass - - -class VerifyAgent298(VerifyAgent): - pass - - -class VerifyAgent299(VerifyAgent1): - pass - - -class VerificationItem149(VerificationItem): - pass - - -class DeclaredBy150(DeclaredBy): - pass - - -class VerifyAgent300(VerifyAgent): - pass - - -class VerifyAgent301(VerifyAgent1): - pass - - -class VerificationItem150(VerificationItem): - pass - - -class DeclaredBy151(DeclaredBy): - pass - - -class VerifyAgent302(VerifyAgent): - pass - - -class VerifyAgent303(VerifyAgent1): - pass - - -class VerificationItem151(VerificationItem): - pass - - -class DeclaredBy152(DeclaredBy): - pass - - -class VerifyAgent304(VerifyAgent): - pass - - -class VerifyAgent305(VerifyAgent1): - pass - - -class VerificationItem152(VerificationItem): - pass - - -class DeclaredBy153(DeclaredBy): - pass - - -class VerifyAgent306(VerifyAgent): - pass - - -class VerifyAgent307(VerifyAgent1): - pass - - -class VerificationItem153(VerificationItem): - pass - - -class DeclaredBy154(DeclaredBy): - pass - - -class VerifyAgent308(VerifyAgent): - pass - - -class VerifyAgent309(VerifyAgent1): - pass - - -class VerificationItem154(VerificationItem): - pass - - -class ReferenceAsset6(ReferenceAsset): - pass - - -class FeedFieldMapping8(FeedFieldMapping): - pass - - -class ReferenceAuthorization6(ReferenceAuthorization): - pass - - -class DeclaredBy155(DeclaredBy): - pass - - -class VerifyAgent310(VerifyAgent): - pass - - -class VerifyAgent311(VerifyAgent1): - pass - - -class VerificationItem155(VerificationItem): - pass - - -class DeclaredBy156(DeclaredBy): - pass - - -class VerifyAgent312(VerifyAgent): - pass - - -class VerifyAgent313(VerifyAgent1): - pass - - -class VerificationItem156(VerificationItem): - pass - - -class DeclaredBy157(DeclaredBy): - pass - - -class VerifyAgent314(VerifyAgent): - pass - - -class VerifyAgent315(VerifyAgent1): - pass - - -class VerificationItem157(VerificationItem): - pass - - -class DeclaredBy158(DeclaredBy): - pass - - -class VerifyAgent316(VerifyAgent): - pass - - -class VerifyAgent317(VerifyAgent1): - pass - - -class VerificationItem158(VerificationItem): - pass - - -class DeclaredBy159(DeclaredBy): - pass - - -class VerifyAgent318(VerifyAgent): - pass - - -class VerifyAgent319(VerifyAgent1): - pass - - -class VerificationItem159(VerificationItem): - pass - - -class DeclaredBy160(DeclaredBy): - pass - - -class VerifyAgent320(VerifyAgent): - pass - - -class VerifyAgent321(VerifyAgent1): - pass - - -class VerificationItem160(VerificationItem): - pass - - -class DeclaredBy161(DeclaredBy): - pass - - -class VerifyAgent322(VerifyAgent): - pass - - -class VerifyAgent323(VerifyAgent1): - pass - - -class VerificationItem161(VerificationItem): - pass - - -class DeclaredBy162(DeclaredBy): - pass - - -class VerifyAgent324(VerifyAgent): - pass - - -class VerifyAgent325(VerifyAgent1): - pass - - -class VerificationItem162(VerificationItem): - pass - - -class DeclaredBy163(DeclaredBy): - pass - - -class VerifyAgent326(VerifyAgent): - pass - - -class VerifyAgent327(VerifyAgent1): - pass - - -class VerificationItem163(VerificationItem): - pass - - -class DeclaredBy164(DeclaredBy): - pass - - -class VerifyAgent328(VerifyAgent): - pass - - -class VerifyAgent329(VerifyAgent1): - pass - - -class VerificationItem164(VerificationItem): - pass - - -class DeclaredBy165(DeclaredBy): - pass - - -class VerifyAgent330(VerifyAgent): - pass - - -class VerifyAgent331(VerifyAgent1): - pass - - -class VerificationItem165(VerificationItem): - pass - - -class DeclaredBy166(DeclaredBy): - pass - - -class VerifyAgent332(VerifyAgent): - pass - - -class VerifyAgent333(VerifyAgent1): - pass - - -class VerificationItem166(VerificationItem): - pass - - -class DeclaredBy167(DeclaredBy): - pass - - -class VerifyAgent334(VerifyAgent): - pass - - -class VerifyAgent335(VerifyAgent1): - pass - - -class VerificationItem167(VerificationItem): - pass - - -class DeclaredBy168(DeclaredBy): - pass - - -class VerifyAgent336(VerifyAgent): - pass - - -class VerifyAgent337(VerifyAgent1): - pass - - -class VerificationItem168(VerificationItem): - pass - - -class DeclaredBy169(DeclaredBy): - pass - - -class VerifyAgent338(VerifyAgent): - pass - - -class VerifyAgent339(VerifyAgent1): - pass - - -class VerificationItem169(VerificationItem): - pass - - -class DeclaredBy170(DeclaredBy): - pass - - -class VerifyAgent340(VerifyAgent): - pass - - -class VerifyAgent341(VerifyAgent1): - pass - - -class VerificationItem170(VerificationItem): - pass - - -class DeclaredBy171(DeclaredBy): - pass - - -class VerifyAgent342(VerifyAgent): - pass - - -class VerifyAgent343(VerifyAgent1): - pass - - -class VerificationItem171(VerificationItem): - pass - - -class DeclaredBy172(DeclaredBy): - pass - - -class VerifyAgent344(VerifyAgent): - pass - - -class VerifyAgent345(VerifyAgent1): - pass - - -class VerificationItem172(VerificationItem): - pass - - -class DeclaredBy173(DeclaredBy): - pass - - -class VerifyAgent346(VerifyAgent): - pass - - -class VerifyAgent347(VerifyAgent1): - pass - - -class VerificationItem173(VerificationItem): - pass - - -class DeclaredBy174(DeclaredBy): - pass - - -class VerifyAgent348(VerifyAgent): - pass - - -class VerifyAgent349(VerifyAgent1): - pass - - -class VerificationItem174(VerificationItem): - pass - - -class DeclaredBy175(DeclaredBy): - pass - - -class VerifyAgent350(VerifyAgent): - pass - - -class VerifyAgent351(VerifyAgent1): - pass - - -class VerificationItem175(VerificationItem): - pass - - -class DeclaredBy176(DeclaredBy): - pass - - -class VerifyAgent352(VerifyAgent): - pass - - -class VerifyAgent353(VerifyAgent1): - pass - - -class VerificationItem176(VerificationItem): - pass - - -class ReferenceAsset7(ReferenceAsset): - pass - - -class FeedFieldMapping9(FeedFieldMapping): - pass - - -class ReferenceAuthorization7(ReferenceAuthorization): - pass - - -class DeclaredBy177(DeclaredBy): - pass - - -class VerifyAgent354(VerifyAgent): - pass - - -class VerifyAgent355(VerifyAgent1): - pass - - -class VerificationItem177(VerificationItem): - pass - - -class DeclaredBy178(DeclaredBy): - pass - - -class VerifyAgent356(VerifyAgent): - pass - - -class VerifyAgent357(VerifyAgent1): - pass - - -class VerificationItem178(VerificationItem): - pass - - -class DeclaredBy179(DeclaredBy): - pass - - -class VerifyAgent358(VerifyAgent): - pass - - -class VerifyAgent359(VerifyAgent1): - pass - - -class VerificationItem179(VerificationItem): - pass - - -class DeclaredBy180(DeclaredBy): - pass - - -class VerifyAgent360(VerifyAgent): - pass - - -class VerifyAgent361(VerifyAgent1): - pass - - -class VerificationItem180(VerificationItem): - pass - - -class DeclaredBy181(DeclaredBy): - pass - - -class VerifyAgent362(VerifyAgent): - pass - - -class VerifyAgent363(VerifyAgent1): - pass - - -class VerificationItem181(VerificationItem): - pass - - -class DeclaredBy182(DeclaredBy): - pass - - -class VerifyAgent364(VerifyAgent): - pass - - -class VerifyAgent365(VerifyAgent1): - pass - - -class VerificationItem182(VerificationItem): - pass - - -class DeclaredBy183(DeclaredBy): - pass - - -class VerifyAgent366(VerifyAgent): - pass - - -class VerifyAgent367(VerifyAgent1): - pass - - -class VerificationItem183(VerificationItem): - pass - - -class DeclaredBy184(DeclaredBy): - pass - - -class VerifyAgent368(VerifyAgent): - pass - - -class VerifyAgent369(VerifyAgent1): - pass - - -class VerificationItem184(VerificationItem): - pass - - -class DeclaredBy185(DeclaredBy): - pass - - -class VerifyAgent370(VerifyAgent): - pass - - -class VerifyAgent371(VerifyAgent1): - pass - - -class VerificationItem185(VerificationItem): - pass - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class AttributionModel(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DayOfWeek(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class AgeVerificationMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class TravelTimeUnit(StrEnum): - min = 'min' - hr = 'hr' - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class DistanceUnit(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class CreativeStatus(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class OptimizationGoals1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target1 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: EventType - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When present without a value-oriented target, the seller may use it for delivery reporting (conversion_value, roas) but must not change the optimization objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - float | None, - Field( - description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class AttributionWindow(AdCPBaseModel): - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target2 | Target3 | Target4 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent3 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1(Jurisdiction): - pass - - -class Disclosure1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo1 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride1 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target5 | Target6 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals(RootModel[OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3]): - root: Annotated[ - OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas12(GeoPostalAreas1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas13(GeoPostalAreas2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas14(GeoPostalAreas3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas15(GeoPostalAreas4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas16(GeoPostalAreas5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas17(GeoPostalAreas6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas18(GeoPostalAreas7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas19(GeoPostalAreas8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas20(GeoPostalAreas9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas21(GeoPostalAreas10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21 - ] -): - root: Annotated[ - GeoPostalAreas12 - | GeoPostalAreas13 - | GeoPostalAreas14 - | GeoPostalAreas15 - | GeoPostalAreas16 - | GeoPostalAreas17 - | GeoPostalAreas18 - | GeoPostalAreas19 - | GeoPostalAreas20 - | GeoPostalAreas21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude11(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude12(GeoPostalAreasExclude1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude13(GeoPostalAreasExclude2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude14(GeoPostalAreasExclude3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude15(GeoPostalAreasExclude4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude16(GeoPostalAreasExclude5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude17(GeoPostalAreasExclude6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude18(GeoPostalAreasExclude7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude19(GeoPostalAreasExclude8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude20(GeoPostalAreasExclude9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude21(GeoPostalAreasExclude10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21 - ] -): - root: Annotated[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude22(GeoPostalAreas22): - pass - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[DayOfWeek], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window1 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AgeVerificationMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: TravelTimeUnit - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: DistanceUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas22] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude22] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class KeywordTargetsAddItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price. Inherits currency and max_bid interpretation from the package's pricing option.", - ge=0.0, - ), - ] = None - - -class KeywordTargetsRemoveItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to stop targeting', min_length=1)] - match_type: MatchType - - -class NegativeKeywordsAddItem(NegativeKeyword): - pass - - -class NegativeKeywordsRemoveItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to stop excluding', min_length=1)] - match_type: MatchType - - -class EmbeddedProvenanceItem2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent4 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent5 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction2(Jurisdiction): - pass - - -class Disclosure2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction2] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy2 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem2] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark2] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure2 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem2] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance2 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent6 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent7 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction3(Jurisdiction): - pass - - -class Disclosure3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction3] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy3 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem3] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark3] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure3 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem3] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance3 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent8 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent9 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction4(Jurisdiction): - pass - - -class Disclosure4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction4] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy4 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem4] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark4] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure4 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem4] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance4 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent10 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent11 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction5(Jurisdiction): - pass - - -class Disclosure5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction5] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy5 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem5] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark5] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure5 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem5] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance5 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent12 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent13 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction6(Jurisdiction): - pass - - -class Disclosure6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction6] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy6 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem6] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark6] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure6 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem6] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance6 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent14 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent15 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction7(Jurisdiction): - pass - - -class Disclosure7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction7] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy7 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem7] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark7] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure7 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem7] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance7 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent16 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent17 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction8(Jurisdiction): - pass - - -class Disclosure8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction8] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy8 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem8] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark8] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure8 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem8] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance8 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent18 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent19 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction9(Jurisdiction): - pass - - -class Disclosure9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction9] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy9 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem9] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark9] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure9 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem9] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance9 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent20 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent21 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction10(Jurisdiction): - pass - - -class Disclosure10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction10] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy10 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem10] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark10] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure10 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem10] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance10 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent22 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent23 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction11(Jurisdiction): - pass - - -class Disclosure11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction11] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy11 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem11] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark11] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure11 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem11] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance11 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent24 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent25 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction12(Jurisdiction): - pass - - -class Disclosure12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction12] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy12 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem12] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark12] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure12 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem12] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance12 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent26 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent27 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction13(Jurisdiction): - pass - - -class Disclosure13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction13] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy13 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem13] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark13] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure13 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem13] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance13 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent28 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent29 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction14(Jurisdiction): - pass - - -class Disclosure14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction14] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy14 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem14] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark14] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure14 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem14] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance14 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent30 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent31 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction15(Jurisdiction): - pass - - -class Disclosure15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction15] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy15 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem15] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark15] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure15 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem15] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance15 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction16] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets15(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets16(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping1] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent32 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy16 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem16] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark16] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure16 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem16] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance16 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction18(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction18] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent36 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy18 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem18] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark18] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure18 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem18] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance18 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction20(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction20] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent40 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction21(Jurisdiction): - pass - - -class Disclosure20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction21] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy20 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem20] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark20] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure20 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem20] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media1, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance20 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent42 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent43 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction22(Jurisdiction): - pass - - -class Disclosure21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction22] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy21 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem21] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark21] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure21 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem21] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance21 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent44 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent45 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction23(Jurisdiction): - pass - - -class Disclosure22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction23] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy22 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem22] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark22] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure22 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem22] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance22 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent46 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent47 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction24(Jurisdiction): - pass - - -class Disclosure23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction24] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy23 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem23] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark23] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure23 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem23] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance23 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent48 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent49 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction25(Jurisdiction): - pass - - -class Disclosure24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction25] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy24 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem24] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark24] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure24 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem24] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance24 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent50 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent51 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction26(Jurisdiction): - pass - - -class Disclosure25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction26] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy25 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem25] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark25] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure25 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem25] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance25 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent52 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent53 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction27(Jurisdiction): - pass - - -class Disclosure26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction27] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy26 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem26] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark26] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure26 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem26] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance26 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent54 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent55 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction28(Jurisdiction): - pass - - -class Disclosure27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction28] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy27 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem27] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark27] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure27 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem27] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance27 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent56 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent57 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction29(Jurisdiction): - pass - - -class Disclosure28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction29] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy28 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem28] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark28] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure28 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem28] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance28 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent58 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent59 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction30(Jurisdiction): - pass - - -class Disclosure29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction30] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy29 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem29] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark29] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure29 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem29] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance29 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent60 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent61 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction31(Jurisdiction): - pass - - -class Disclosure30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction31] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy30 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem30] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark30] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure30 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem30] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance30 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent62 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent63 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction32(Jurisdiction): - pass - - -class Disclosure31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction32] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy31 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem31] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark31] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure31 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem31] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance31 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent64 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent65 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction33(Jurisdiction): - pass - - -class Disclosure32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction33] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy32 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem32] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark32] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure32 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem32] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance32 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent66 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent67 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction34(Jurisdiction): - pass - - -class Disclosure33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction34] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy33 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem33] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark33] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure33 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem33] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance33 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent68 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent69 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction35(Jurisdiction): - pass - - -class Disclosure34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction35] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy34 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem34] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark34] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure34 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem34] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance34 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent70 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent71 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction36(Jurisdiction): - pass - - -class Disclosure35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction36] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy35 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem35] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark35] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure35 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem35] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance35 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent72 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent73 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction37(Jurisdiction): - pass - - -class Disclosure36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction37] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy36 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem36] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark36] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure36 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem36] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance36 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent74 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent75 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction38(Jurisdiction): - pass - - -class Disclosure37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction38] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy37 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem37] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark37] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure37 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem37] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance37 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure1(RequiredDisclosure): - pass - - -class Compliance1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure1] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets2217(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset1] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance1 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets2218(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping2] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent76 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent77 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction40(Jurisdiction): - pass - - -class Disclosure38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction40] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy38 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem38] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark38] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure38 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem38] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization1 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance38 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent78 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent79 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction41(Jurisdiction): - pass - - -class Disclosure39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction41] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy39 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem39] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark39] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure39 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem39] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance39 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent80 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent81 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction42(Jurisdiction): - pass - - -class Disclosure40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction42] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy40 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem40] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark40] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure40 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem40] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance40 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent82 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent83 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction43(Jurisdiction): - pass - - -class Disclosure41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction43] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy41 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem41] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark41] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure41 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem41] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance41 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent84 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent85 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction44(Jurisdiction): - pass - - -class Disclosure42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction44] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy42 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem42] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark42] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure42 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem42] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media2 | Media3, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl1 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance42 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent86 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent87 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction45(Jurisdiction): - pass - - -class Disclosure43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction45] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy43 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem43] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark43] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure43 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem43] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance43 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent88 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent89 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction46(Jurisdiction): - pass - - -class Disclosure44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction46] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy44 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem44] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark44] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure44 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem44] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance44 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent90 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent91 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction47(Jurisdiction): - pass - - -class Disclosure45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction47] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy45 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem45] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark45] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure45 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem45] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance45 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets221( - RootModel[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223 - ] -): - root: Annotated[ - Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets2210 - | Assets2211 - | Assets2212 - | Assets2213 - | Assets2214 - | Assets2215 - | Assets14 - | Assets2217 - | Assets2218 - | Assets2219 - | Assets2220 - | Assets2221 - | Assets2222 - | Assets2223, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets22(RootModel[list[Assets221]]): - root: Annotated[list[Assets221], Field(min_length=1)] - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: CreativeIdentifierType - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class EmbeddedProvenanceItem46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent92 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent93 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction48(Jurisdiction): - pass - - -class Disclosure46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction48] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy46 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem46] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark46] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure46 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem46] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets1 - | Assets2 - | Assets3 - | Assets4 - | Assets5 - | Assets6 - | Assets7 - | Assets8 - | Assets9 - | Assets10 - | Assets11 - | Assets12 - | Assets13 - | Assets14 - | Assets15 - | Assets16 - | Assets17 - | Assets18 - | Assets19 - | Assets20 - | Assets21 - | Assets22, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: CreativeStatus | None = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance46 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent94 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent95 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction49(Jurisdiction): - pass - - -class Disclosure47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction49] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy47 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem47] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark47] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure47 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem47] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance47 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent96 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent97 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction50(Jurisdiction): - pass - - -class Disclosure48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction50] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy48 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem48] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark48] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure48 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem48] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance48 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent98 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent99 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction51(Jurisdiction): - pass - - -class Disclosure49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction51] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy49 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem49] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark49] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure49 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem49] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance49 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction52(Jurisdiction): - pass - - -class Disclosure50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction52] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy50 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem50] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark50] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure50 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem50] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance50 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction53(Jurisdiction): - pass - - -class Disclosure51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction53] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy51 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem51] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark51] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure51 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem51] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance51 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction54(Jurisdiction): - pass - - -class Disclosure52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction54] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy52 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem52] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark52] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure52 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem52] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance52 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction55(Jurisdiction): - pass - - -class Disclosure53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction55] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy53 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem53] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark53] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure53 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem53] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance53 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction56(Jurisdiction): - pass - - -class Disclosure54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction56] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy54 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem54] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark54] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure54 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem54] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance54 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent110 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent111 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction57(Jurisdiction): - pass - - -class Disclosure55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction57] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy55 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem55] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark55] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure55 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem55] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance55 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent112 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent113 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction58(Jurisdiction): - pass - - -class Disclosure56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction58] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy56 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem56] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark56] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure56 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem56] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance56 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent114 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent115 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction59(Jurisdiction): - pass - - -class Disclosure57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction59] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy57 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem57] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark57] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure57 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem57] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance57 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent116 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent117 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction60(Jurisdiction): - pass - - -class Disclosure58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction60] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy58 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem58] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark58] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure58 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem58] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance58 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent118 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent119 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction61(Jurisdiction): - pass - - -class Disclosure59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction61] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy59 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem59] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark59] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure59 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem59] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance59 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent120 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent121 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction62(Jurisdiction): - pass - - -class Disclosure60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction62] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy60 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem60] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark60] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure60 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem60] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance60 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets37(Assets14): - pass - - -class RequiredDisclosure2(RequiredDisclosure): - pass - - -class Compliance2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure2] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets38(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset2] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance2 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets39(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping3] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent122 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent123 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction64(Jurisdiction): - pass - - -class Disclosure61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction64] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy61 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem61] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark61] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure61 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem61] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization2 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance61 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent124 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent125 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction65(Jurisdiction): - pass - - -class Disclosure62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction65] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy62 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem62] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark62] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure62 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem62] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance62 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent126 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent127 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction66(Jurisdiction): - pass - - -class Disclosure63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction66] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy63 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem63] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark63] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure63 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem63] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance63 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent128 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent129 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction67(Jurisdiction): - pass - - -class Disclosure64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction67] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy64 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem64] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark64] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure64 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem64] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance64 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent130 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent131 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction68(Jurisdiction): - pass - - -class Disclosure65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction68] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy65 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem65] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark65] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure65 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem65] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media4 | Media5, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl2 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance65 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent132 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent133 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction69(Jurisdiction): - pass - - -class Disclosure66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction69] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy66 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem66] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark66] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure66 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem66] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance66 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent134 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent135 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction70(Jurisdiction): - pass - - -class Disclosure67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction70] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy67 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem67] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark67] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure67 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem67] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance67 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent136 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent137 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction71(Jurisdiction): - pass - - -class Disclosure68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction71] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy68 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem68] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark68] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure68 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem68] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance68 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent138 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent139 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction72(Jurisdiction): - pass - - -class Disclosure69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction72] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy69 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem69] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark69] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure69 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem69] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance69 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent140 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent141 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction73(Jurisdiction): - pass - - -class Disclosure70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction73] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy70 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem70] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark70] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure70 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem70] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance70 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent142 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent143 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction74(Jurisdiction): - pass - - -class Disclosure71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction74] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy71 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem71] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark71] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure71 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem71] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance71 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent144 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent145 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction75(Jurisdiction): - pass - - -class Disclosure72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction75] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy72 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem72] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark72] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure72 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem72] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance72 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent146 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent147 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction76(Jurisdiction): - pass - - -class Disclosure73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction76] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy73 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem73] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark73] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure73 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem73] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance73 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent148 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent149 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction77(Jurisdiction): - pass - - -class Disclosure74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction77] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy74 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem74] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark74] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure74 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem74] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance74 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent150 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent151 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction78(Jurisdiction): - pass - - -class Disclosure75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction78] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy75 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem75] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark75] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure75 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem75] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance75 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent152 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent153 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction79(Jurisdiction): - pass - - -class Disclosure76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction79] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy76 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem76] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark76] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure76 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem76] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance76 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent154 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent155 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction80(Jurisdiction): - pass - - -class Disclosure77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction80] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy77 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem77] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark77] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure77 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem77] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance77 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent156 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent157 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction81(Jurisdiction): - pass - - -class Disclosure78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction81] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy78 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem78] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark78] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure78 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem78] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance78 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent158 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent159 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction82(Jurisdiction): - pass - - -class Disclosure79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction82] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy79 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem79] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark79] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure79 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem79] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance79 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent160 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent161 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction83(Jurisdiction): - pass - - -class Disclosure80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction83] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy80 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem80] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark80] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure80 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem80] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance80 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent162 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent163 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction84(Jurisdiction): - pass - - -class Disclosure81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction84] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy81 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem81] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark81] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure81 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem81] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance81 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent164 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent165 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction85(Jurisdiction): - pass - - -class Disclosure82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction85] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy82 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem82] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark82] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure82 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem82] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance82 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure3(RequiredDisclosure): - pass - - -class Compliance3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure3] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets4517(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset3] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance3 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets4518(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping4] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent166 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent167 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction87(Jurisdiction): - pass - - -class Disclosure83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction87] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy83 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem83] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark83] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure83 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem83] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization3 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance83 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent168 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent169 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction88(Jurisdiction): - pass - - -class Disclosure84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction88] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy84 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem84] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark84] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure84 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem84] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance84 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent170 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent171 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction89(Jurisdiction): - pass - - -class Disclosure85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction89] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy85 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem85] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark85] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure85 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem85] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance85 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent172 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent173 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction90(Jurisdiction): - pass - - -class Disclosure86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction90] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy86 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem86] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark86] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure86 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem86] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance86 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent174 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent175 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction91(Jurisdiction): - pass - - -class Disclosure87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction91] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy87 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem87] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark87] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure87 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem87] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media6 | Media7, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl3 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance87 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent176 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent177 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction92(Jurisdiction): - pass - - -class Disclosure88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction92] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy88 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem88] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark88] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure88 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem88] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance88 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent178 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent179 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction93(Jurisdiction): - pass - - -class Disclosure89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction93] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy89 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem89] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark89] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure89 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem89] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance89 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent180 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent181 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction94(Jurisdiction): - pass - - -class Disclosure90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction94] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy90 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem90] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark90] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure90 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem90] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets4523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance90 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets451( - RootModel[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523 - ] -): - root: Annotated[ - Assets452 - | Assets453 - | Assets454 - | Assets455 - | Assets456 - | Assets457 - | Assets458 - | Assets459 - | Assets4510 - | Assets4511 - | Assets4512 - | Assets4513 - | Assets4514 - | Assets4515 - | Assets14 - | Assets4517 - | Assets4518 - | Assets4519 - | Assets4520 - | Assets4521 - | Assets4522 - | Assets4523, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets45(RootModel[list[Assets451]]): - root: Annotated[list[Assets451], Field(min_length=1)] - - -class EmbeddedProvenanceItem91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent182 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent183 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction95(Jurisdiction): - pass - - -class Disclosure91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction95] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy91 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem91] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark91] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure91 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem91] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId | None, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets23 - | Assets24 - | Assets25 - | Assets26 - | Assets27 - | Assets28 - | Assets29 - | Assets30 - | Assets31 - | Assets32 - | Assets33 - | Assets34 - | Assets35 - | Assets36 - | Assets37 - | Assets38 - | Assets39 - | Assets40 - | Assets41 - | Assets42 - | Assets43 - | Assets44 - | Assets45, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: CreativeStatus | None = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance91 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's ID of package to update")] - budget: Annotated[ - float | None, - Field( - description='Updated budget allocation for this package in the currency specified by the pricing option', - ge=0.0, - ), - ] = None - pacing: Pacing | None = None - bid_price: Annotated[ - float | None, - Field( - description="Updated bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Updated impression goal for this package', ge=0.0) - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Updated flight start date/time for this package in ISO 8601 format. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Updated flight end date/time for this package in ISO 8601 format. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - bool | None, - Field(description='Pause/resume specific package (true = paused, false = active)'), - ] = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Cancel this specific package. Cancellation is irreversible — canceled packages stop delivery and cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE.' - ), - ] = None - cancellation_reason: Annotated[ - str | None, Field(description='Reason for canceling this package.', max_length=500) - ] = None - catalogs: Annotated[ - list[Catalog] | None, - Field( - description='Replace the catalogs this package promotes. Uses replacement semantics — the provided array replaces the current list. Omit to leave catalogs unchanged.', - min_length=1, - ), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals] | None, - Field( - description='Replace all optimization goals for this package. Uses replacement semantics — omit to leave goals unchanged.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description="Targeting overlay to apply to this package. Uses replacement semantics — the full overlay replaces the previous one. Omit to leave targeting unchanged. For keyword and negative keyword updates, prefer the incremental operations (keyword_targets_add, keyword_targets_remove, negative_keywords_add, negative_keywords_remove) which avoid replacing the full overlay. Sellers SHOULD return a validation error if targeting_overlay.keyword_targets is present in the same request as keyword_targets_add or keyword_targets_remove, and likewise for negative_keywords. If the replacement changes signal_targeting_groups, sellers MAY require a new quote or reject with REQUOTE_REQUIRED when the selected signal, group expression, or pricing_option_id changes the package's priced envelope.", - title='Targeting Overlay', - ), - ] = None - keyword_targets_add: Annotated[ - list[KeywordTargetsAddItem] | None, - Field( - description='Keyword targets to add or update on this package. Upserts by (keyword, match_type) identity: if the pair already exists, its bid_price is updated; if not, a new keyword target is added. Use targeting_overlay.keyword_targets in create_media_buy to set the initial list.', - min_length=1, - ), - ] = None - keyword_targets_remove: Annotated[ - list[KeywordTargetsRemoveItem] | None, - Field( - description='Keyword targets to remove from this package. Removes matching (keyword, match_type) pairs. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', - min_length=1, - ), - ] = None - negative_keywords_add: Annotated[ - list[NegativeKeywordsAddItem] | None, - Field( - description='Negative keywords to add to this package. Appends to the existing negative keyword list — does not replace it. If a keyword+match_type pair already exists, sellers SHOULD treat it as a no-op for that entry. Use targeting_overlay.negative_keywords in create_media_buy to set the initial list.', - min_length=1, - ), - ] = None - negative_keywords_remove: Annotated[ - list[NegativeKeywordsRemoveItem] | None, - Field( - description='Negative keywords to remove from this package. Removes matching keyword+match_type pairs from the existing list. If a specified pair is not present, sellers SHOULD treat it as a no-op for that entry.', - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment] | None, - Field( - description='Replace creative assignments for this package with optional weights and placement targeting. Uses replacement semantics - omit to leave assignments unchanged.' - ), - ] = None - creatives: Annotated[ - list[Creatives | Creatives1] | None, - Field( - description="Replace this package's inline creative assets. When the seller also advertises creative.has_creative_library: true, new inline creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Catalog1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping5] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class OptimizationGoals5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - Metric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency1 | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target1 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class AttributionWindow1(AdCPBaseModel): - post_click: Annotated[ - PostClick1 | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView1 | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target2 | Target3 | Target4 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow1 | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent184 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent185 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction96(Jurisdiction): - pass - - -class Disclosure92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction96] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy92 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem92] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark92] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure92 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem92] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance92 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo2 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride2 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor1, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target5 | Target6 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals4(RootModel[OptimizationGoals5 | OptimizationGoals6 | OptimizationGoals7]): - root: Annotated[ - OptimizationGoals5 | OptimizationGoals6 | OptimizationGoals7, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas2311(GeoPostalAreas11): - pass - - -class GeoPostalAreas2312(GeoPostalAreas231): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2313(GeoPostalAreas232): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2314(GeoPostalAreas233): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2315(GeoPostalAreas234): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2316(GeoPostalAreas235): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2317(GeoPostalAreas236): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2318(GeoPostalAreas237): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2319(GeoPostalAreas238): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2320(GeoPostalAreas239): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas2321(GeoPostalAreas2310): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas23( - RootModel[ - GeoPostalAreas2312 - | GeoPostalAreas2313 - | GeoPostalAreas2314 - | GeoPostalAreas2315 - | GeoPostalAreas2316 - | GeoPostalAreas2317 - | GeoPostalAreas2318 - | GeoPostalAreas2319 - | GeoPostalAreas2320 - | GeoPostalAreas2321 - ] -): - root: Annotated[ - GeoPostalAreas2312 - | GeoPostalAreas2313 - | GeoPostalAreas2314 - | GeoPostalAreas2315 - | GeoPostalAreas2316 - | GeoPostalAreas2317 - | GeoPostalAreas2318 - | GeoPostalAreas2319 - | GeoPostalAreas2320 - | GeoPostalAreas2321, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas24(GeoPostalAreas22): - pass - - -class GeoPostalAreasExclude2311(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude2312(GeoPostalAreasExclude231): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2313(GeoPostalAreasExclude232): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2314(GeoPostalAreasExclude233): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2315(GeoPostalAreasExclude234): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2316(GeoPostalAreasExclude235): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2317(GeoPostalAreasExclude236): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2318(GeoPostalAreasExclude237): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2319(GeoPostalAreasExclude238): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2320(GeoPostalAreasExclude239): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2321(GeoPostalAreasExclude2310): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude23( - RootModel[ - GeoPostalAreasExclude2312 - | GeoPostalAreasExclude2313 - | GeoPostalAreasExclude2314 - | GeoPostalAreasExclude2315 - | GeoPostalAreasExclude2316 - | GeoPostalAreasExclude2317 - | GeoPostalAreasExclude2318 - | GeoPostalAreasExclude2319 - | GeoPostalAreasExclude2320 - | GeoPostalAreasExclude2321 - ] -): - root: Annotated[ - GeoPostalAreasExclude2312 - | GeoPostalAreasExclude2313 - | GeoPostalAreasExclude2314 - | GeoPostalAreasExclude2315 - | GeoPostalAreasExclude2316 - | GeoPostalAreasExclude2317 - | GeoPostalAreasExclude2318 - | GeoPostalAreasExclude2319 - | GeoPostalAreasExclude2320 - | GeoPostalAreasExclude2321, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude24(GeoPostalAreas22): - pass - - -class FrequencyCap1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress1 | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window3 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class GeoProximityItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry1 | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TargetingOverlay1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - list[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - list[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - list[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas23 | GeoPostalAreas24] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - list[GeoPostalAreasExclude23 | GeoPostalAreasExclude24] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups1 | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting4] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap1 | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem1] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent186 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent187 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction97(Jurisdiction): - pass - - -class Disclosure93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction97] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy93 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem93] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark93] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure93 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem93] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance93 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor2, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent188 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent189 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction98(Jurisdiction): - pass - - -class Disclosure94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction98] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy94 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem94] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark94] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure94 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem94] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance94 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo4 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride4 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: Annotated[ - Metric2, - Field( - description='The performance metric this standard applies to.', - title='Performance Standard Metric', - ), - ] - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor3, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: Annotated[ - CompletionSource | None, - Field( - description="Trust-source disambiguator for `completion_rate` — *who* attested to the completion event, not *how* (methodology granularity is a separate dimension; future qualifier keys may add it if buyer demand surfaces). The two paths can yield materially different rates, particularly in SSAI environments where the player's view of completion may differ from a vendor's. Used as a `qualifier.completion_source` key on `committed_metrics`, `missing_metrics`, and `metric_aggregates` to disambiguate which trust source the row represents. Edge cases: walled gardens where the seller is also the measurement vendor (YouTube, Spotify) collapse to `seller_attested` by trust-model logic — the same party served and counted. IAB-certified first-party podcast measurement (Podtrac, Triton on their own platforms; Art19 on its own platform) likewise collapses to `seller_attested`. The same vendor's offering on a third-party platform (Podtrac on a publisher who isn't Podtrac) is `vendor_attested`. The trust axis is *not* who runs the SDK — it's who is independent of the seller's revenue interest.", - title='Completion Source', - ), - ] = None - attribution_methodology: Annotated[ - AttributionMethodology | None, - Field( - description='How attribution between ad exposure and outcome events was computed. Used as a `qualifier.attribution_methodology` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate the same outcome metric reported under different methodologies — `conversion_value` measured deterministically (matched purchase IDs) is not the same number as `conversion_value` measured probabilistically (modeled match) and should never be summed across methodologies. The retail-media closed-loop pattern typically reports under `deterministic_purchase`; MMM and clean-room outputs typically report under `modeled` or `probabilistic`; panel-based measurement (Nielsen, comScore, Edison) reports under `panel_based`.', - title='Attribution Methodology', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow2 | None, - Field( - description="A time duration expressed as an interval and unit. Used for frequency cap windows, attribution windows, reach optimization windows, time budgets, and other time-based settings. When unit is 'campaign', interval must be 1 — the window spans the full campaign flight.", - title='Duration', - ), - ] = None - lift_dimension: Annotated[ - LiftDimension | None, - Field( - description='Brand-lift dimension disambiguator. Brand lift is multidimensional in production — Kantar, Upwave, Cint, DoubleVerify, and similar vendors report awareness, consideration, favorability, purchase intent, and ad recall as separate measurements with their own sample sizes and confidence intervals. Used as a `qualifier.lift_dimension` key on `committed_metrics`, `missing_metrics`, `metric_aggregates`, and `performance-feedback.metric` to disambiguate which dimension of `brand_lift` a row represents. Two `brand_lift` rows under different lift dimensions represent different surveyed outcomes and must not be combined into a single number.', - title='Lift Dimension', - ), - ] = None - - -class CommittedMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description='Disambiguator — same shape as on the response-side `committed_metrics`. Required when the buyer wants to pin a specific measurement path: `viewability_standard` for MRC vs GroupM viewability; `completion_source` for seller- vs vendor-attested `completion_rate`; `attribution_methodology` for how attribution was computed (deterministic_purchase, probabilistic, panel_based, modeled); `attribution_window` for the time window over which outcomes are attributed. See response-side description for full semantics.' - ), - ] = None - - -class EmbeddedProvenanceItem95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent190 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent191 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction99(Jurisdiction): - pass - - -class Disclosure95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction99] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy95 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem95] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark95] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure95 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem95] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance95 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo5 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride5 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor4, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. MUST be present in the product's `reporting_capabilities.vendor_metrics` for the same vendor.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class CommittedMetrics(RootModel[CommittedMetrics1 | CommittedMetrics2]): - root: Annotated[CommittedMetrics1 | CommittedMetrics2, Field(discriminator='scope')] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class EmbeddedProvenanceItem96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent192 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent193 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction100(Jurisdiction): - pass - - -class Disclosure96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction100] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy96 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem96] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark96] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure96 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem96] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance96 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent194 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent195 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction101(Jurisdiction): - pass - - -class Disclosure97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction101] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy97 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem97] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark97] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure97 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem97] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance97 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent196 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent197 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction102(Jurisdiction): - pass - - -class Disclosure98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction102] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy98 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem98] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark98] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure98 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem98] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance98 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent198 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent199 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction103(Jurisdiction): - pass - - -class Disclosure99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction103] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy99 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem99] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark99] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure99 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem99] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance99 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent200 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent201 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction104(Jurisdiction): - pass - - -class Disclosure100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction104] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy100 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem100] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark100] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure100 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem100] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance100 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent202 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent203 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction105(Jurisdiction): - pass - - -class Disclosure101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction105] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy101 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem101] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark101] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure101 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem101] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance101 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent204 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent205 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction106(Jurisdiction): - pass - - -class Disclosure102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction106] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy102 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem102] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark102] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure102 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem102] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance102 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent206 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent207 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction107(Jurisdiction): - pass - - -class Disclosure103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction107] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy103 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem103] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark103] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure103 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem103] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance103 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent208 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent209 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction108(Jurisdiction): - pass - - -class Disclosure104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction108] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy104 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem104] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark104] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure104 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem104] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance104 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent210 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent211 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction109(Jurisdiction): - pass - - -class Disclosure105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction109] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy105 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem105] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark105] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure105 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem105] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance105 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent212 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent213 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction110(Jurisdiction): - pass - - -class Disclosure106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction110] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy106 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem106] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark106] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure106 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem106] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance106 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent214 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent215 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction111(Jurisdiction): - pass - - -class Disclosure107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction111] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy107 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem107] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark107] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure107 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem107] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance107 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent216 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent217 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction112(Jurisdiction): - pass - - -class Disclosure108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction112] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy108 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem108] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark108] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure108 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem108] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance108 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent218 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent219 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction113(Jurisdiction): - pass - - -class Disclosure109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction113] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance109(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy109 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem109] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark109] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure109 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem109] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance109 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets60(Assets14): - pass - - -class RequiredDisclosure4(RequiredDisclosure): - pass - - -class Compliance4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure4] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets61(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset4] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance4 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets62(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping6] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent220 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent221 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction115(Jurisdiction): - pass - - -class Disclosure110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction115] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy110 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem110] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark110] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure110 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem110] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization4 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance110 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent222 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent223 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction116(Jurisdiction): - pass - - -class Disclosure111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction116] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy111 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem111] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark111] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure111 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem111] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance111 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent224 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent225 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction117(Jurisdiction): - pass - - -class Disclosure112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction117] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy112 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem112] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark112] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure112 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem112] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance112 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent226 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent227 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction118(Jurisdiction): - pass - - -class Disclosure113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction118] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy113 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem113] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark113] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure113 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem113] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance113 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent228 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent229 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction119(Jurisdiction): - pass - - -class Disclosure114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction119] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy114 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem114] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark114] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure114 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem114] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media8 | Media9, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl4 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance114 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent230 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent231 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction120(Jurisdiction): - pass - - -class Disclosure115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction120] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy115 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem115] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark115] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure115 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem115] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance115 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent232 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent233 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction121(Jurisdiction): - pass - - -class Disclosure116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction121] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance116(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy116 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem116] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark116] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure116 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem116] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance116 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent234 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent235 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction122(Jurisdiction): - pass - - -class Disclosure117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction122] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance117(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy117 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem117] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark117] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure117 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem117] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance117 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent236 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent237 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction123(Jurisdiction): - pass - - -class Disclosure118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction123] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance118(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy118 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem118] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark118] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure118 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem118] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance118 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent238 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent239 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction124(Jurisdiction): - pass - - -class Disclosure119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction124] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy119 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem119] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark119] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure119 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem119] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance119 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent240 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent241 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction125(Jurisdiction): - pass - - -class Disclosure120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction125] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy120 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem120] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark120] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure120 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem120] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance120 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent242 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent243 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction126(Jurisdiction): - pass - - -class Disclosure121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction126] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy121 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem121] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark121] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure121 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem121] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance121 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent244 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent245 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction127(Jurisdiction): - pass - - -class Disclosure122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction127] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy122 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem122] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark122] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure122 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem122] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance122 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent246 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent247 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction128(Jurisdiction): - pass - - -class Disclosure123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction128] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy123 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem123] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark123] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure123 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem123] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance123 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent248 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent249 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction129(Jurisdiction): - pass - - -class Disclosure124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction129] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy124 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem124] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark124] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure124 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem124] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance124 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent250 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent251 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction130(Jurisdiction): - pass - - -class Disclosure125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction130] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy125 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem125] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark125] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure125 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem125] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance125 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent252 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent253 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction131(Jurisdiction): - pass - - -class Disclosure126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction131] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance126(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy126 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem126] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark126] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure126 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem126] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance126 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent254 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent255 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction132(Jurisdiction): - pass - - -class Disclosure127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction132] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance127(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy127 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem127] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark127] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure127 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem127] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance127 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent256 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent257 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction133(Jurisdiction): - pass - - -class Disclosure128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction133] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance128(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy128 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem128] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark128] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure128 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem128] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance128 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent258 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent259 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction134(Jurisdiction): - pass - - -class Disclosure129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction134] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance129(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy129 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem129] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark129] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure129 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem129] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance129 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent260 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent261 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction135(Jurisdiction): - pass - - -class Disclosure130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction135] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance130(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy130 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem130] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark130] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure130 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem130] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance130 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent262 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent263 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction136(Jurisdiction): - pass - - -class Disclosure131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction136] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy131 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem131] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark131] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure131 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem131] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance131 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure5(RequiredDisclosure): - pass - - -class Compliance5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure5] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets6817(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset5] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance5 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets6818(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping7] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent264 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent265 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction138(Jurisdiction): - pass - - -class Disclosure132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction138] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy132 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem132] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark132] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure132 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem132] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization5 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance132 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent266 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent267 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction139(Jurisdiction): - pass - - -class Disclosure133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction139] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy133 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem133] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark133] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure133 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem133] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance133 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent268 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent269 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction140(Jurisdiction): - pass - - -class Disclosure134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction140] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy134 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem134] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark134] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure134 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem134] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance134 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent270 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent271 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction141(Jurisdiction): - pass - - -class Disclosure135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction141] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy135 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem135] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark135] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure135 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem135] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance135 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent272 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent273 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction142(Jurisdiction): - pass - - -class Disclosure136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction142] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance136(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy136 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem136] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark136] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure136 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem136] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media10 | Media11, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl5 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance136 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent274 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent275 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction143(Jurisdiction): - pass - - -class Disclosure137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction143] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance137(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy137 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem137] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark137] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure137 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem137] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance137 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent276 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent277 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction144(Jurisdiction): - pass - - -class Disclosure138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction144] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance138(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy138 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem138] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark138] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure138 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem138] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance138 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent278 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent279 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction145(Jurisdiction): - pass - - -class Disclosure139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction145] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance139(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy139 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem139] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark139] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure139 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem139] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets6823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance139 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets681( - RootModel[ - Assets682 - | Assets683 - | Assets684 - | Assets685 - | Assets686 - | Assets687 - | Assets688 - | Assets689 - | Assets6810 - | Assets6811 - | Assets6812 - | Assets6813 - | Assets6814 - | Assets6815 - | Assets14 - | Assets6817 - | Assets6818 - | Assets6819 - | Assets6820 - | Assets6821 - | Assets6822 - | Assets6823 - ] -): - root: Annotated[ - Assets682 - | Assets683 - | Assets684 - | Assets685 - | Assets686 - | Assets687 - | Assets688 - | Assets689 - | Assets6810 - | Assets6811 - | Assets6812 - | Assets6813 - | Assets6814 - | Assets6815 - | Assets14 - | Assets6817 - | Assets6818 - | Assets6819 - | Assets6820 - | Assets6821 - | Assets6822 - | Assets6823, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets68(RootModel[list[Assets681]]): - root: Annotated[list[Assets681], Field(min_length=1)] - - -class EmbeddedProvenanceItem140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent280 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent281 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction146(Jurisdiction): - pass - - -class Disclosure140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction146] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance140(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy140 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem140] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark140] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure140 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem140] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets46 - | Assets47 - | Assets48 - | Assets49 - | Assets50 - | Assets51 - | Assets52 - | Assets53 - | Assets54 - | Assets55 - | Assets56 - | Assets57 - | Assets58 - | Assets59 - | Assets60 - | Assets61 - | Assets62 - | Assets63 - | Assets64 - | Assets65 - | Assets66 - | Assets67 - | Assets68, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: CreativeStatus | None = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance140 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent282 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent283 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction147(Jurisdiction): - pass - - -class Disclosure141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction147] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy141 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem141] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark141] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure141 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem141] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance141 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent284 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent285 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction148(Jurisdiction): - pass - - -class Disclosure142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction148] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy142 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem142] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark142] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure142 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem142] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance142 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent286 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent287 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction149(Jurisdiction): - pass - - -class Disclosure143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction149] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy143 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem143] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark143] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure143 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem143] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance143 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent288 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent289 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction150(Jurisdiction): - pass - - -class Disclosure144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction150] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy144 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem144] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark144] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure144 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem144] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance144 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent290 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent291 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction151(Jurisdiction): - pass - - -class Disclosure145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction151] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy145 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem145] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark145] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure145 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem145] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance145 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent292 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent293 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction152(Jurisdiction): - pass - - -class Disclosure146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction152] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy146 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem146] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark146] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure146 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem146] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance146 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent294 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent295 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction153(Jurisdiction): - pass - - -class Disclosure147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction153] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy147 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem147] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark147] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure147 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem147] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance147 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent296 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent297 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction154(Jurisdiction): - pass - - -class Disclosure148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction154] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy148 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem148] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark148] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure148 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem148] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance148 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent298 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent299 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction155(Jurisdiction): - pass - - -class Disclosure149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction155] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy149 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem149] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark149] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure149 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem149] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance149 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent300 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent301 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction156(Jurisdiction): - pass - - -class Disclosure150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction156] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy150 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem150] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark150] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure150 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem150] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance150 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent302 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent303 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction157(Jurisdiction): - pass - - -class Disclosure151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction157] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy151 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem151] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark151] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure151 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem151] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance151 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent304 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent305 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction158(Jurisdiction): - pass - - -class Disclosure152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction158] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy152 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem152] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark152] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure152 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem152] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance152 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent306 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent307 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction159(Jurisdiction): - pass - - -class Disclosure153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction159] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy153 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem153] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark153] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure153 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem153] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance153 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent308 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent309 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction160(Jurisdiction): - pass - - -class Disclosure154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction160] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy154 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem154] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark154] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure154 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem154] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance154 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets83(Assets14): - pass - - -class RequiredDisclosure6(RequiredDisclosure): - pass - - -class Compliance6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure6] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets84(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset6] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance6 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets85(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping8] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent310 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent311 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction162(Jurisdiction): - pass - - -class Disclosure155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction162] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy155 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem155] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark155] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure155 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem155] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization6 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance155 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent312 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent313 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction163(Jurisdiction): - pass - - -class Disclosure156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction163] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy156 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem156] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark156] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure156 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem156] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance156 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent314 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent315 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction164(Jurisdiction): - pass - - -class Disclosure157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction164] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy157 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem157] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark157] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure157 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem157] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance157 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent316 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent317 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction165(Jurisdiction): - pass - - -class Disclosure158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction165] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy158 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem158] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark158] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure158 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem158] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance158 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent318 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent319 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction166(Jurisdiction): - pass - - -class Disclosure159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction166] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy159 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem159] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark159] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure159 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem159] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media12 | Media13, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl6 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance159 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent320 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent321 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction167(Jurisdiction): - pass - - -class Disclosure160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction167] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy160 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem160] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark160] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure160 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem160] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance160 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent322 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent323 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction168(Jurisdiction): - pass - - -class Disclosure161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction168] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance161(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy161 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem161] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark161] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure161 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem161] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance161 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent324 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent325 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction169(Jurisdiction): - pass - - -class Disclosure162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction169] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance162(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy162 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem162] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark162] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure162 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem162] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance162 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent326 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent327 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction170(Jurisdiction): - pass - - -class Disclosure163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction170] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy163 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem163] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark163] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure163 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem163] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance163 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent328 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent329 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction171(Jurisdiction): - pass - - -class Disclosure164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction171] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy164 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem164] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark164] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure164 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem164] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance164 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent330 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent331 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction172(Jurisdiction): - pass - - -class Disclosure165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction172] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy165 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem165] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark165] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure165 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem165] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance165 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent332 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent333 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction173(Jurisdiction): - pass - - -class Disclosure166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction173] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy166 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem166] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark166] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure166 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem166] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance166 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent334 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent335 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction174(Jurisdiction): - pass - - -class Disclosure167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction174] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy167 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem167] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark167] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure167 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem167] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VASTVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance167 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent336 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent337 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction175(Jurisdiction): - pass - - -class Disclosure168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction175] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance168(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy168 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem168] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark168] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure168 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem168] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance168 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent338 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent339 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction176(Jurisdiction): - pass - - -class Disclosure169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction176] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy169 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem169] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark169] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure169 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem169] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance169 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent340 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent341 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction177(Jurisdiction): - pass - - -class Disclosure170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction177] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy170 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem170] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark170] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure170 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem170] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance170 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent342 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent343 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction178(Jurisdiction): - pass - - -class Disclosure171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction178] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy171 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem171] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark171] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure171 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem171] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance171 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent344 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent345 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction179(Jurisdiction): - pass - - -class Disclosure172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction179] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy172 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem172] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark172] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure172 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem172] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance172 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent346 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent347 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction180(Jurisdiction): - pass - - -class Disclosure173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction180] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy173 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem173] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark173] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure173 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem173] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance173 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent348 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent349 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction181(Jurisdiction): - pass - - -class Disclosure174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction181] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy174 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem174] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark174] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure174 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem174] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance174 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent350 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent351 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction182(Jurisdiction): - pass - - -class Disclosure175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction182] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy175 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem175] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark175] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure175 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem175] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance175 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent352 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent353 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction183(Jurisdiction): - pass - - -class Disclosure176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction183] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy176 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem176] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark176] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure176 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem176] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DAASTVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance176 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure7(RequiredDisclosure): - pass - - -class Compliance7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure7] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets9117(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset7] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance7 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets9118(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping9] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent354 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent355 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction185(Jurisdiction): - pass - - -class Disclosure177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction185] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy177 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem177] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark177] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure177 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem177] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization7 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance177 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent356 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent357 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction186(Jurisdiction): - pass - - -class Disclosure178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction186] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy178 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem178] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark178] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure178 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem178] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance178 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent358 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent359 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction187(Jurisdiction): - pass - - -class Disclosure179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction187] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy179 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem179] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark179] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure179 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem179] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance179 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent360 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent361 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction188(Jurisdiction): - pass - - -class Disclosure180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction188] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy180 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem180] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark180] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure180 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem180] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance180 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent362 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent363 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction189(Jurisdiction): - pass - - -class Disclosure181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction189] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy181 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem181] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark181] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure181 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem181] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media14 | Media15, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl7 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance181 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent364 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent365 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction190(Jurisdiction): - pass - - -class Disclosure182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction190] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy182 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem182] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark182] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure182 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem182] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance182 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent366 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent367 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction191(Jurisdiction): - pass - - -class Disclosure183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction191] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance183(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy183 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem183] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark183] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure183 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem183] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target7 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target7.linear - provenance: Annotated[ - Provenance183 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent368 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent369 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction192(Jurisdiction): - pass - - -class Disclosure184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction192] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance184(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy184 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem184] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark184] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure184 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem184] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets9123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target8 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target8.linear - provenance: Annotated[ - Provenance184 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets911( - RootModel[ - Assets912 - | Assets913 - | Assets914 - | Assets915 - | Assets916 - | Assets917 - | Assets918 - | Assets919 - | Assets9110 - | Assets9111 - | Assets9112 - | Assets9113 - | Assets9114 - | Assets9115 - | Assets14 - | Assets9117 - | Assets9118 - | Assets9119 - | Assets9120 - | Assets9121 - | Assets9122 - | Assets9123 - ] -): - root: Annotated[ - Assets912 - | Assets913 - | Assets914 - | Assets915 - | Assets916 - | Assets917 - | Assets918 - | Assets919 - | Assets9110 - | Assets9111 - | Assets9112 - | Assets9113 - | Assets9114 - | Assets9115 - | Assets14 - | Assets9117 - | Assets9118 - | Assets9119 - | Assets9120 - | Assets9121 - | Assets9122 - | Assets9123, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets91(RootModel[list[Assets911]]): - root: Annotated[list[Assets911], Field(min_length=1)] - - -class EmbeddedProvenanceItem185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent370 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent371 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction193(Jurisdiction): - pass - - -class Disclosure185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction193] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance185(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy185 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem185] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark185] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure185 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem185] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creatives3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[ - str, - Field( - description='Unique identifier for the creative. Stable across legacy named-format and 3.1+ canonical-format paths — a creative registered against `format_id` retains the same `creative_id` when later viewed through a canonical-format flatten.' - ), - ] - name: Annotated[str, Field(description='Human-readable creative name')] - format_id: Annotated[ - FormatId | None, - Field( - description='Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier specifying which format this creative conforms to. Can be: (1) concrete format_id referencing a format with fixed dimensions, (2) template format_id referencing a template format, or (3) parameterized format_id with dimensions/duration parameters for template formats. Mutually exclusive with `format_kind`.', - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRef | FormatOptionRef1 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product has multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the creative to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets69 - | Assets70 - | Assets71 - | Assets72 - | Assets73 - | Assets74 - | Assets75 - | Assets76 - | Assets77 - | Assets78 - | Assets79 - | Assets80 - | Assets81 - | Assets82 - | Assets83 - | Assets84 - | Assets85 - | Assets86 - | Assets87 - | Assets88 - | Assets89 - | Assets90 - | Assets91, - ], - Field( - description='Assets required by the format, keyed by asset_id or canonical asset_group_id. Each slot value is either a single asset object or an array of asset objects (for slots with `min`/`max > 1` like carousel `cards` or responsive_creative `headlines`). Each asset value carries an `asset_type` discriminator that selects the matching asset schema, including reference assets such as `published_post` when a product accepts already-published post references.' - ), - ] - inputs: Annotated[ - list[Input] | None, - Field( - description='Preview contexts for generative formats - defines what scenarios to generate previews for' - ), - ] = None - tags: Annotated[ - list[str] | None, Field(description='User-defined tags for organization and searchability') - ] = None - status: CreativeStatus | None = None - weight: Annotated[ - float | None, - Field( - description='Optional delivery weight for creative rotation when uploading via create_media_buy or update_media_buy (0-100). If omitted, platform determines rotation. Only used during upload to media buy - not stored in creative library.', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description='Optional structured placement references where this uploaded creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use this field for placement-level targeting because placement IDs are publisher-scoped. References product placements by `{ publisher_domain, placement_id }`. If omitted, creative runs on all buyer-targetable placements. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description='Legacy shorthand array of placement IDs where this creative should run when uploading via create_media_buy or update_media_buy. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. If omitted, creative runs on all buyer-targetable placements. If `placement_refs` is also present, receivers MUST ignore this field. Only used during upload to media buy - not stored in creative library.', - min_length=1, - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this creative (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). In broadcast and scheduled audio/video buying, these identifiers tie the creative to rotation instructions, clearance records, and traffic systems. A creative may have multiple identifiers when different systems reference the same asset. Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance185 | None, - Field( - description='Provenance metadata for this creative. Serves as the default provenance for all manifests and assets within this creative. A manifest or asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - - -class NewPackage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - product_id: Annotated[ - str, - Field( - description='Product ID for this package. Sellers MUST echo this value on every response package object that represents this requested package.' - ), - ] - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format selector. Array of format IDs that will be used for this package - must be supported by the product. If omitted (and no 3.1+ format-option selector or direct canonical selector is present), defaults to all formats supported by the product.\n\nSellers comparing this selector to a product's `format_options[]` MUST first normalize each legacy `format_id` through the canonical mapping path (`canonical`, `v1_format_ref`, or registry projection). Exact `(agent_url, id)` comparison after projection is insufficient: a legacy fixed-size display ID can satisfy a canonical `image` product declaration with matching `width`/`height`. Product gating remains directional: if the product declares fixed dimensions or duration, the selected format must declare and match those constraints; an under-specified canonical request is not a wildcard for a fixed-size or fixed-duration product. Range constraints use containment, not overlap: a range-based request satisfies the product only when every value it permits falls within the product's accepted range.", - min_length=1, - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs] | None, - Field( - description='3.1+ format-option selector. Array of structured format option references, each matching one of the target product\'s `format_options[]` entries. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. If omitted along with `format_ids` and direct `format_kind`, all product formats are active.\n\n**Resolution rules (normative).**\n- **Both `format_option_refs` and `format_ids` present.** `format_option_refs` wins; the seller routes by structured references and MUST NOT validate `format_ids` for consistency with the resolved declarations. The `format_ids` value is a legacy-compat hint for intermediaries on the wire path; the resolving seller ignores it.\n- **`format_option_refs` only.** Seller looks up each entry against the package\'s target product `format_options[]` and uses the matching declaration (and that declaration\'s `v1_format_ref[]` when projecting to legacy named-format surfaces). This is the 3.1+ format-option authoring path. `scope: "product"` is scoped only by this target product; it is not a seller-wide identifier.\n- **`format_ids` only.** Existing named-format behavior; unchanged.\n- **`format_kind` only.** Direct canonical selector behavior; seller compares `{ format_kind, params }` against the product\'s `format_options[]` declarations using directional product satisfaction.\n- **None of `format_option_refs`, `format_ids`, or `format_kind`.** Default — all formats supported by the product are active.\n\n**Failure modes (normative).** Sellers MUST reject with `UNSUPPORTED_FEATURE` (with `field` pointing at the failing package and entry, e.g. `packages[0].format_option_refs[1]`) when:\n- Any entry references a format option not present in the target product\'s `format_options[]`, OR\n- The target product carries `format_ids` but no `format_options[]` (legacy-format-only product — there is no closed set to resolve against), OR\n- The target product carries `format_options[]` but none of the entries publish selectable `format_option_id` values. Sellers SHOULD set `error.details.reason` to `format_option_refs_not_published` in this case so buyers can distinguish it from an outright mismatch and fall back to `format_ids[]`.\n\n**Seller obligation.** For buyers to use the 3.1+ format-option path against a product, the seller MUST publish a selectable `format_option_id` on each `format_options[]` entry it expects buyers to select; if the option is publisher-catalog backed, include `publisher_domain` on the product declaration and require buyers to use `scope: "publisher"` in `FormatOptionRef`.\n\n**No legacy capability selector.** `capability_ids` was removed before GA; schemas reject it instead of treating it as an extension.\n\n**Dual emission.** Format-option-aware buyer SDKs targeting a heterogeneous seller population SHOULD emit `format_ids` alongside `format_option_refs` so legacy-format-only sellers — which ignore unknown fields per `additionalProperties: true` — still receive an explicit format set rather than silently defaulting to all formats supported by the product.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description="Parameters for the direct canonical selector in `format_kind`. Shape follows the selected canonical's parameter vocabulary: dimensions (`width`, `height`, `sizes`), duration (`duration_ms_exact`, `duration_ms_range`), codecs, asset-source and slot narrowing, or other canonical-specific constraints. Omit when selecting by `format_option_refs` or `format_ids`; those selectors resolve their parameters from the product declaration or legacy catalog projection." - ), - ] = None - budget: Annotated[ - float, - Field(description="Budget allocation for this package in the media buy's currency", ge=0.0), - ] - pacing: Pacing | None = None - pricing_option_id: Annotated[ - str, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing options. This is the exact bid/price to honor unless selected pricing_option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Must fall within the media buy's date range." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Must fall within the media buy's date range." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package should be created in a paused state. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - catalogs: Annotated[ - list[Catalog1] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Makes the package catalog-driven: one budget envelope, platform optimizes across items.' - ), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals4] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay1 | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Buyer's proposed billing measurement and makegood terms. Overrides product defaults. Seller accepts (echoed on confirmed package), rejects with TERMS_REJECTED, or adjusts. When absent, product's measurement_terms apply.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Buyer's proposed performance standards for this package. Overrides product defaults. Seller accepts, rejects with TERMS_REJECTED, or adjusts. When absent, product's performance_standards apply.", - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics] | None, - Field( - description="Buyer's proposed reporting contract for this package — the metrics the buyer wants the seller to commit to populating in delivery reports. Same negotiation pattern as `measurement_terms` and `performance_standards`: seller accepts (echoes on confirmed package with `committed_at` stamped), rejects with `TERMS_REJECTED` (with explanation of which entries were unworkable), or normalizes (echoes a different but compatible list — buyer can accept by retrying with the normalized terms). When absent, the seller decides what to commit based on the product's `available_metrics` and the buyer's `required_metrics` filter on `get_products`. Each entry uses an explicit `scope` discriminator (`standard` or `vendor`) and identifies the metric — request-side entries do NOT carry `committed_at`; that timestamp is stamped by the seller on accept. Constraints on what the buyer MAY propose: each `scope: standard` entry's `metric_id` MUST be in the product's `available_metrics`, and each `scope: vendor` entry's `(vendor, metric_id)` MUST appear in the product's `vendor_metrics` — sellers SHOULD reject with `TERMS_REJECTED` and reference the offending entry when the proposal exceeds product capability.", - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment1] | None, - Field( - description='Assign existing library creatives to this package with optional weights and placement targeting', - min_length=1, - ), - ] = None - creatives: Annotated[ - list[Creatives2 | Creatives3] | None, - Field( - description="Upload creative assets inline and assign to this package. When the seller also advertises creative.has_creative_library: true, these creatives enter the seller's creative library and can be reused by creative_id while retained; inline-only sellers may store them as package-scoped assets. Use creative_assignments instead for existing library creatives.", - max_length=100, - min_length=1, - ), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description='Agency estimate or authorization number for this package. Overrides the media buy-level estimate number when different packages correspond to different agency estimates (e.g., different stations or flights within the same buy).', - max_length=100, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in the package response, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Do not use deprecated top-level buyer_ref for v3 correlation.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD use the RFC 9421 webhook signing profile instead.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class ReportingWebhook(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[AnyUrl, Field(description='Webhook endpoint URL for reporting notifications')] - token: Annotated[ - str | None, - Field( - description='Optional client-provided token for webhook validation. Echoed back in webhook payload to validate request authenticity.', - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication, - Field( - description="Legacy authentication configuration for webhook delivery (A2A-compatible). Opts the receiver into Bearer or HMAC-SHA256 signing. Both schemes are deprecated; the preferred signing profile for new integrations is RFC 9421, where the seller signs with a key published at its brand.json agents[] entry and the buyer verifies against the seller's JWKS — no shared secret crosses the wire (see docs/building/implementation/security.mdx#webhook-callbacks). This field is required in AdCP 3.x; the requirement is removed in AdCP 4.0 when the default RFC 9421 path becomes the only path." - ), - ] - reporting_frequency: Annotated[ - ReportingFrequency, - Field( - description='Frequency for automated reporting delivery. Must be supported by all products in the media buy.' - ), - ] - requested_metrics: Annotated[ - list[AvailableMetric] | None, - Field( - description="Optional list of metrics to include in webhook notifications. If omitted, all available metrics are included. Must be subset of product's available_metrics." - ), - ] = None - - -class Authentication1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication1 | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class UpdateMediaBuyRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account1, - Field( - description='Account that owns this media buy. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. Required for governance checks and account resolution.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] - media_buy_id: Annotated[str, Field(description="Seller's ID of the media buy to update")] - revision: Annotated[ - int | None, - Field( - description="Expected current revision for optimistic concurrency. Optional for backward compatibility. When provided, sellers MUST reject the update with CONFLICT if the media buy's current revision does not match, and MUST enforce that comparison atomically with the write. Obtain from get_media_buys or the most recent create/update response.", - ge=1, - ), - ] = None - paused: Annotated[ - bool | None, - Field(description='Pause/resume the entire media buy (true = paused, false = active)'), - ] = None - canceled: Annotated[ - Literal[True] | None, - Field( - description='Cancel the entire media buy. Cancellation is irreversible — canceled media buys cannot be reactivated. Sellers MAY reject with NOT_CANCELLABLE if the media buy cannot be canceled in its current state.' - ), - ] = None - cancellation_reason: Annotated[ - str | None, - Field( - description='Reason for cancellation. Sellers SHOULD store this and return it in subsequent get_media_buys responses.', - max_length=500, - ), - ] = None - start_time: Annotated[ - Literal['asap'] | AwareDatetime | None, - Field( - description="Campaign start timing: 'asap' or ISO 8601 date-time", title='Start Timing' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='New end date/time in ISO 8601 format') - ] = None - packages: Annotated[ - Sequence[Package] | None, - Field(description='Package-specific updates for existing packages', min_length=1), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient | None, - Field( - description="Update who receives the invoice for this buy. When provided, the seller invoices this entity instead of the account's default billing_entity. The seller MUST validate the invoice recipient is authorized for this account. When governance_agents are configured, the seller MUST include invoice_recipient in the check_governance request.", - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - new_packages: Annotated[ - list[NewPackage] | None, - Field( - description='New packages to add to this media buy. Uses the same schema as create_media_buy packages. Sellers that support mid-flight package additions advertise `add_packages` in both `valid_actions[]` (deprecated) and as an entry in `available_actions[]` (authoritative). Sellers that do not support this MUST reject with ACTION_NOT_ALLOWED (preferred) or UNSUPPORTED_FEATURE (legacy).', - min_length=1, - ), - ] = None - reporting_webhook: Annotated[ - ReportingWebhook | None, - Field( - description='Optional webhook configuration for automated reporting delivery. Updates the reporting configuration for this media buy.', - title='Reporting Webhook', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async update notifications. Publisher will send webhook when update completes if operation takes longer than immediate response time. This is separate from reporting_webhook which configures ongoing campaign reporting.', - title='Push Notification Config', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated idempotency key for safe retries. If an update fails without a response, resending with the same idempotency_key guarantees the update is applied at most once. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_response.py b/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_response.py deleted file mode 100644 index 1120653e..00000000 --- a/src/adcp/types/generated_poc/bundled/media_buy/update_media_buy_response.py +++ /dev/null @@ -1,493 +0,0 @@ -# generated by datamodel-codegen: -# filename: update_media_buy_response.json -# timestamp: 2026-06-18T11:31:45+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class UpdateMediaBuyResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class PostalCodeSystem(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' diff --git a/src/adcp/types/generated_poc/bundled/property/__init__.py b/src/adcp/types/generated_poc/bundled/property/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/property/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/property/create_property_list_request.py b/src/adcp/types/generated_poc/bundled/property/create_property_list_request.py deleted file mode 100644 index 5e86ca02..00000000 --- a/src/adcp/types/generated_poc/bundled/property/create_property_list_request.py +++ /dev/null @@ -1,1129 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/create_property_list_request.json -# timestamp: 2026-05-28T10:34:10+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent33(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy17(DeclaredBy): - pass - - -class VerifyAgent34(VerifyAgent): - pass - - -class VerifyAgent35(VerifyAgent33): - pass - - -class VerificationItem17(VerificationItem): - pass - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent33 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties(RootModel[BaseProperties1 | BaseProperties2 | BaseProperties3]): - root: Annotated[ - BaseProperties1 | BaseProperties2 | BaseProperties3, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent34 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent35 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction17(Jurisdiction): - pass - - -class Disclosure17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction17] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy17 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem17] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark17] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure17 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem17] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance17 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo3 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride3 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CreatePropertyListRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account5 | None, - Field( - description='Account that will own the list. Pass a natural key (brand, operator, optional sandbox) or a seller-assigned account_id from list_accounts. When omitted, this task applies its task-local single-account shortcut: if exactly one account is accessible to the authenticated caller, the seller may assign the list to that account; otherwise it MUST return an account-required or ambiguous-account error. Omission MUST NOT mean an undocumented credential-local default account.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - base_properties: Annotated[ - list[BaseProperties] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database.", - min_length=1, - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Dynamic filters to apply when resolving the list', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand3 | None, - Field( - description='Brand reference. When provided, the agent automatically applies appropriate rules based on brand characteristics (industry, target_audience, etc.). Resolved at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate property list creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/create_property_list_response.py b/src/adcp/types/generated_poc/bundled/property/create_property_list_response.py deleted file mode 100644 index 4eb32c5a..00000000 --- a/src/adcp/types/generated_poc/bundled/property/create_property_list_response.py +++ /dev/null @@ -1,1655 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/create_property_list_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent37(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy19(DeclaredBy): - pass - - -class VerifyAgent38(VerifyAgent): - pass - - -class VerifyAgent39(VerifyAgent37): - pass - - -class VerificationItem19(VerificationItem): - pass - - -class AppliesToOutputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class PricingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption6(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption7(PricingOption1, PricingOption6): - pass - - -class PricingOption8(PricingOption2, PricingOption6): - pass - - -class PricingOption9(PricingOption3, PricingOption6): - pass - - -class PricingOption10(PricingOption4, PricingOption6): - pass - - -class PricingOption11(PricingOption5, PricingOption6): - pass - - -class PricingOption( - RootModel[PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11] -): - root: Annotated[ - PricingOption7 | PricingOption8 | PricingOption9 | PricingOption10 | PricingOption11, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent37 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties4(RootModel[BaseProperties | BaseProperties6 | BaseProperties7]): - root: Annotated[ - BaseProperties | BaseProperties6 | BaseProperties7, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent38 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent39 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction19(Jurisdiction): - pass - - -class Disclosure19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction19] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy19 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem19] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark19] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure19 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem19] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance19 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo5 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride5 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class List(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this property list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - Account | Account7 | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - base_properties: Annotated[ - list[BaseProperties4] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Dynamic filters applied when resolving the list', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand5 | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - int | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', - ge=1, - ), - ] = 24 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - property_count: Annotated[ - int | None, - Field(description='Number of properties in the resolved list (at time of last resolution)'), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - - -class CreatePropertyListResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - list: Annotated[List, Field(description='The created property list', title='Property List')] - auth_token: Annotated[ - str, - Field( - description='Token that can be shared with sellers to authorize fetching this list. Store this - it is only returned at creation time.' - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/delete_property_list_request.py b/src/adcp/types/generated_poc/bundled/property/delete_property_list_request.py deleted file mode 100644 index a1e86839..00000000 --- a/src/adcp/types/generated_poc/bundled/property/delete_property_list_request.py +++ /dev/null @@ -1,627 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/delete_property_list_request.json -# timestamp: 2026-05-28T10:34:10+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent41(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent41 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class DeletePropertyListRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list to delete')] - account: Annotated[ - Account | Account9 | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/bundled/property/delete_property_list_response.py b/src/adcp/types/generated_poc/bundled/property/delete_property_list_response.py deleted file mode 100644 index f0ce5815..00000000 --- a/src/adcp/types/generated_poc/bundled/property/delete_property_list_response.py +++ /dev/null @@ -1,314 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/delete_property_list_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class DeletePropertyListResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - deleted: Annotated[bool, Field(description='Whether the list was successfully deleted')] - list_id: Annotated[str, Field(description='ID of the deleted list')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/get_property_list_request.py b/src/adcp/types/generated_poc/bundled/property/get_property_list_request.py deleted file mode 100644 index f4722e1a..00000000 --- a/src/adcp/types/generated_poc/bundled/property/get_property_list_request.py +++ /dev/null @@ -1,644 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/get_property_list_request.json -# timestamp: 2026-06-04T19:44:00+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent425(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent425 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, - Field(description='Maximum number of identifiers to return per page', ge=1, le=10000), - ] = 1000 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class GetPropertyListRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list to retrieve')] - account: Annotated[ - Account | Account19 | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts and the list_id is not globally unique within that scope; optional otherwise.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - resolve: Annotated[ - bool | None, - Field( - description='Whether to apply filters and return resolved identifiers (default: true)' - ), - ] = True - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Uses higher limits than standard pagination because property lists can contain tens of thousands of identifiers.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/get_property_list_response.py b/src/adcp/types/generated_poc/bundled/property/get_property_list_response.py deleted file mode 100644 index 6829c4fe..00000000 --- a/src/adcp/types/generated_poc/bundled/property/get_property_list_response.py +++ /dev/null @@ -1,1716 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/get_property_list_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent427(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy214(DeclaredBy): - pass - - -class VerifyAgent428(VerifyAgent): - pass - - -class VerifyAgent429(VerifyAgent427): - pass - - -class VerificationItem214(VerificationItem): - pass - - -class AppliesToOutputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class PricingOption121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption124(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption125(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption126(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption127(PricingOption121, PricingOption126): - pass - - -class PricingOption128(PricingOption122, PricingOption126): - pass - - -class PricingOption129(PricingOption123, PricingOption126): - pass - - -class PricingOption1210(PricingOption124, PricingOption126): - pass - - -class PricingOption1211(PricingOption125, PricingOption126): - pass - - -class PricingOption( - RootModel[ - PricingOption127 - | PricingOption128 - | PricingOption129 - | PricingOption1210 - | PricingOption1211 - ] -): - root: Annotated[ - PricingOption127 - | PricingOption128 - | PricingOption129 - | PricingOption1210 - | PricingOption1211, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent427 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account21(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties8(RootModel[BaseProperties | BaseProperties10 | BaseProperties11]): - root: Annotated[ - BaseProperties | BaseProperties10 | BaseProperties11, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent428 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent429 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction223(Jurisdiction): - pass - - -class Disclosure214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction223] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy214 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem214] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark214] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure214 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem214] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance214 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo20 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride20 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class List(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this property list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - Account | Account21 | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - base_properties: Annotated[ - list[BaseProperties8] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Dynamic filters applied when resolving the list', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand18 | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - int | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', - ge=1, - ), - ] = 24 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - property_count: Annotated[ - int | None, - Field(description='Number of properties in the resolved list (at time of last resolution)'), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - - -class CoverageGap(Identifier): - pass - - -class GetPropertyListResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - list: Annotated[ - List, - Field(description='The property list metadata (always returned)', title='Property List'), - ] - identifiers: Annotated[ - list[Identifier] | None, - Field( - description='Resolved identifiers that passed filters (if resolve=true). Cache these locally for real-time use.' - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - resolved_at: Annotated[ - AwareDatetime | None, Field(description='When the list was resolved') - ] = None - cache_valid_until: Annotated[ - AwareDatetime | None, - Field( - description='Cache expiration timestamp. Re-fetch the list after this time to get updated identifiers.' - ), - ] = None - coverage_gaps: Annotated[ - dict[str, list[CoverageGap]] | None, - Field( - description="Properties included in the list despite missing feature data. Only present when a feature_requirement has if_not_covered='include'. Maps feature_id to list of identifiers not covered for that feature." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/list_property_lists_request.py b/src/adcp/types/generated_poc/bundled/property/list_property_lists_request.py deleted file mode 100644 index 5e4189f7..00000000 --- a/src/adcp/types/generated_poc/bundled/property/list_property_lists_request.py +++ /dev/null @@ -1,640 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/list_property_lists_request.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1207(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1207 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account34(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class ListPropertyListsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account34 | None, - Field( - description='Filter to lists owned by this account. When omitted, returns lists across all accounts accessible to the authenticated agent.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - name_contains: Annotated[ - str | None, Field(description='Filter to lists whose name contains this string') - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/list_property_lists_response.py b/src/adcp/types/generated_poc/bundled/property/list_property_lists_response.py deleted file mode 100644 index e461ac17..00000000 --- a/src/adcp/types/generated_poc/bundled/property/list_property_lists_response.py +++ /dev/null @@ -1,1691 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/list_property_lists_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent1209(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy605(DeclaredBy): - pass - - -class VerifyAgent1210(VerifyAgent): - pass - - -class VerifyAgent1211(VerifyAgent1209): - pass - - -class VerificationItem605(VerificationItem): - pass - - -class AppliesToOutputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class PricingOption211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption214(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption216(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption217(PricingOption211, PricingOption216): - pass - - -class PricingOption218(PricingOption212, PricingOption216): - pass - - -class PricingOption219(PricingOption213, PricingOption216): - pass - - -class PricingOption2110(PricingOption214, PricingOption216): - pass - - -class PricingOption2111(PricingOption215, PricingOption216): - pass - - -class PricingOption( - RootModel[ - PricingOption217 - | PricingOption218 - | PricingOption219 - | PricingOption2110 - | PricingOption2111 - ] -): - root: Annotated[ - PricingOption217 - | PricingOption218 - | PricingOption219 - | PricingOption2110 - | PricingOption2111, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1209 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account36(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties12(RootModel[BaseProperties | BaseProperties14 | BaseProperties15]): - root: Annotated[ - BaseProperties | BaseProperties14 | BaseProperties15, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem605(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1210 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark605(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1211 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction630(Jurisdiction): - pass - - -class Disclosure607(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction630] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance605(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy605 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem605] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark605] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure607 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem605] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance605 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo85 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand35(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride85 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class List(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this property list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - Account | Account36 | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - base_properties: Annotated[ - list[BaseProperties12] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Dynamic filters applied when resolving the list', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand35 | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - int | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', - ge=1, - ), - ] = 24 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - property_count: Annotated[ - int | None, - Field(description='Number of properties in the resolved list (at time of last resolution)'), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - - -class ListPropertyListsResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - lists: Annotated[ - list[List], - Field(description='Array of property lists (metadata only, not resolved properties)'), - ] - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/update_property_list_request.py b/src/adcp/types/generated_poc/bundled/property/update_property_list_request.py deleted file mode 100644 index 2cba2935..00000000 --- a/src/adcp/types/generated_poc/bundled/property/update_property_list_request.py +++ /dev/null @@ -1,1133 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/update_property_list_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent2475(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy1244(DeclaredBy): - pass - - -class VerifyAgent2476(VerifyAgent): - pass - - -class VerifyAgent2477(VerifyAgent2475): - pass - - -class VerificationItem1238(VerificationItem): - pass - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2475 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account57(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties16(RootModel[BaseProperties | BaseProperties18 | BaseProperties19]): - root: Annotated[ - BaseProperties | BaseProperties18 | BaseProperties19, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem1238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2476 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2477 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1295(Jurisdiction): - pass - - -class Disclosure1241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1295] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1244 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1238] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1238] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1241 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1238] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1237 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo156 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand59(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride155 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class UpdatePropertyListRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list to update')] - account: Annotated[ - Account | Account57 | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - name: Annotated[str | None, Field(description='New name for the list')] = None - description: Annotated[str | None, Field(description='New description')] = None - base_properties: Annotated[ - list[BaseProperties16] | None, - Field( - description='Complete replacement for the base properties list (not a patch). Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers).' - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Complete replacement for the filters (not a patch)', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand59 | None, - Field( - description='Update brand reference. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field( - description='Update the webhook URL for list change notifications (set to empty string to remove)' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. If a request with the same key has already been processed, the server returns the original response without re-processing. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] diff --git a/src/adcp/types/generated_poc/bundled/property/update_property_list_response.py b/src/adcp/types/generated_poc/bundled/property/update_property_list_response.py deleted file mode 100644 index a07bac8e..00000000 --- a/src/adcp/types/generated_poc/bundled/property/update_property_list_response.py +++ /dev/null @@ -1,1659 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/update_property_list_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent2479(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class Tag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class BaseProperties(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_tags'], - Field(description='Discriminator indicating selection by property tags within a publisher'), - ] = 'publisher_tags' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - tags: Annotated[ - list[Tag], - Field( - description="Property tags from the publisher's adagents.json. Selects all properties with these tags.", - min_length=1, - ), - ] - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class BaseProperties22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['publisher_ids'], - Field( - description='Discriminator indicating selection by specific property IDs within a publisher' - ), - ] = 'publisher_ids' - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'raptive.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class CountriesAllItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class ChannelsAnyEnum(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class PropertyType(StrEnum): - website = 'website' - mobile_app = 'mobile_app' - ctv_app = 'ctv_app' - desktop_app = 'desktop_app' - dooh = 'dooh' - podcast = 'podcast' - radio = 'radio' - linear_tv = 'linear_tv' - streaming_audio = 'streaming_audio' - ai_assistant = 'ai_assistant' - - -class IfNotCovered(StrEnum): - exclude = 'exclude' - include = 'include' - - -class FeatureRequirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, Field(description='Feature to evaluate (discovered via get_adcp_capabilities)') - ] - min_value: Annotated[ - float | None, - Field(description='Minimum numeric value required (for quantitative features)'), - ] = None - max_value: Annotated[ - float | None, Field(description='Maximum numeric value allowed (for quantitative features)') - ] = None - allowed_values: Annotated[ - list[Any] | None, - Field( - description='Values that pass the requirement (for binary/categorical features)', - min_length=1, - ), - ] = None - if_not_covered: Annotated[ - IfNotCovered | None, - Field( - description="How to handle properties where this feature is not covered. 'exclude' (default): property is removed from the list. 'include': property passes this requirement (fail-open)." - ), - ] = IfNotCovered.exclude - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this requirement encodes a specific buyer-chosen threshold authorized by a policy, policy_id references the authorizing PolicyEntry. Producers populate when the mechanism exists because of a specific policy (e.g., max_value: 15 on audience_children_composition because of uk_hfss); do NOT populate when the requirement is a general filter unrelated to any policy. Governance findings echo this policy_id when emitting denials traced to the requirement. See /docs/governance/policy-attribution.' - ), - ] = None - - -class DeclaredBy1246(DeclaredBy): - pass - - -class VerifyAgent2480(VerifyAgent): - pass - - -class VerifyAgent2481(VerifyAgent2479): - pass - - -class VerificationItem1240(VerificationItem): - pass - - -class AppliesToOutputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class PricingOption281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption286(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption287(PricingOption281, PricingOption286): - pass - - -class PricingOption288(PricingOption282, PricingOption286): - pass - - -class PricingOption289(PricingOption283, PricingOption286): - pass - - -class PricingOption2810(PricingOption284, PricingOption286): - pass - - -class PricingOption2811(PricingOption285, PricingOption286): - pass - - -class PricingOption( - RootModel[ - PricingOption287 - | PricingOption288 - | PricingOption289 - | PricingOption2810 - | PricingOption2811 - ] -): - root: Annotated[ - PricingOption287 - | PricingOption288 - | PricingOption289 - | PricingOption2810 - | PricingOption2811, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class PropertyIdentifierTypes(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2479 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account59(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: PropertyIdentifierTypes - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class BaseProperties23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - selection_type: Annotated[ - Literal['identifiers'], - Field(description='Discriminator indicating selection by direct identifiers'), - ] = 'identifiers' - identifiers: Annotated[ - list[Identifier], - Field(description='Direct property identifiers (domains, app IDs, etc.)', min_length=1), - ] - - -class BaseProperties20(RootModel[BaseProperties | BaseProperties22 | BaseProperties23]): - root: Annotated[ - BaseProperties | BaseProperties22 | BaseProperties23, - Field( - description='A source of properties for a property list. Supports three selection patterns: publisher with tags, publisher with property IDs, or direct identifiers.', - discriminator='selection_type', - title='Base Property Source', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ExcludeIdentifier(Identifier): - pass - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - countries_all: Annotated[ - list[CountriesAllItem] | None, - Field( - description='Property must have feature data for ALL listed countries (ISO codes). When omitted, no country restriction is applied.', - min_length=1, - ), - ] = None - channels_any: Annotated[ - list[ChannelsAnyEnum] | None, - Field( - description='Property must support ANY of the listed channels. When omitted, no channel restriction is applied.', - min_length=1, - ), - ] = None - property_types: Annotated[ - list[PropertyType] | None, Field(description='Filter to these property types', min_length=1) - ] = None - feature_requirements: Annotated[ - list[FeatureRequirement] | None, - Field( - description='Feature-based requirements. Property must pass ALL requirements (AND logic).', - min_length=1, - ), - ] = None - exclude_identifiers: Annotated[ - list[ExcludeIdentifier] | None, - Field(description='Identifiers to always exclude from results', min_length=1), - ] = None - - -class EmbeddedProvenanceItem1240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2480 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark1240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2481 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction1297(Jurisdiction): - pass - - -class Disclosure1243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction1297] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance1239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy1246 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem1240] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark1240] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure1243 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem1240] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance1239 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo158 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand61(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride157 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class List(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - list_id: Annotated[str, Field(description='Unique identifier for this property list')] - name: Annotated[str, Field(description='Human-readable name for the list')] - description: Annotated[str | None, Field(description="Description of the list's purpose")] = ( - None - ) - account: Annotated[ - Account | Account59 | None, - Field( - description='Account that owns this list. Returned as account_id form (seller-assigned identifier).', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - base_properties: Annotated[ - list[BaseProperties20] | None, - Field( - description="Array of property sources to evaluate. Each entry is a discriminated union: publisher_tags (publisher_domain + tags), publisher_ids (publisher_domain + property_ids), or identifiers (direct identifiers). If omitted, queries the agent's entire property database." - ), - ] = None - filters: Annotated[ - Filters | None, - Field( - description='Dynamic filters applied when resolving the list', - title='Property List Filters', - ), - ] = None - brand: Annotated[ - Brand61 | None, - Field( - description='Brand reference used to automatically apply appropriate rules. Resolved to full brand identity at execution time.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - webhook_url: Annotated[ - AnyUrl | None, - Field(description='URL to receive notifications when the resolved list changes'), - ] = None - cache_duration_hours: Annotated[ - int | None, - Field( - description='Recommended cache duration for resolved list. Consumers should re-fetch after this period.', - ge=1, - ), - ] = 24 - created_at: Annotated[AwareDatetime | None, Field(description='When the list was created')] = ( - None - ) - updated_at: Annotated[ - AwareDatetime | None, Field(description='When the list was last modified') - ] = None - property_count: Annotated[ - int | None, - Field(description='Number of properties in the resolved list (at time of last resolution)'), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options for this property list. Present when the requesting account has a billing relationship with the list provider. The buyer passes the selected pricing_option_id in report_usage.', - min_length=1, - ), - ] = None - - -class UpdatePropertyListResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - list: Annotated[List, Field(description='The updated property list', title='Property List')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_request.py b/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_request.py deleted file mode 100644 index b3ed1c76..00000000 --- a/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_request.py +++ /dev/null @@ -1,718 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/validate_property_delivery_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent2681(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent2681 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account63(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Type(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Type, - Field( - description='Type of identifier', - examples=[ - 'domain', - 'ios_bundle', - 'venue_id', - 'apple_podcast_id', - 'station_id', - 'facility_id', - ], - title='Property Identifier Types', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class Record(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identifier: Annotated[ - Identifier, - Field( - description='The property identifier where impressions were delivered', - title='Identifier', - ), - ] - impressions: Annotated[ - int, Field(description='Number of impressions delivered to this identifier', ge=0) - ] - record_id: Annotated[ - str | None, - Field( - description='Optional client-provided ID for correlating results back to source data' - ), - ] = None - sales_agent_url: Annotated[ - AnyUrl | None, - Field( - description="URL of the sales agent that sold this inventory. If provided, authorization is validated against the property's adagents.json." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class ValidatePropertyDeliveryRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list to validate against')] - account: Annotated[ - Account | Account63 | None, - Field( - description='Account that owns the list. Required when the authenticated agent has access to multiple accounts; optional otherwise.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - records: Annotated[ - list[Record], - Field( - description='Delivery records to validate. Each record represents impressions delivered to a property identifier.', - max_length=10000, - min_length=1, - ), - ] - include_compliant: Annotated[ - bool | None, - Field( - description='Include compliant records in results (default: only return non_compliant, unmodeled, and unidentified)' - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_response.py b/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_response.py deleted file mode 100644 index 369a9deb..00000000 --- a/src/adcp/types/generated_poc/bundled/property/validate_property_delivery_response.py +++ /dev/null @@ -1,623 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/property/validate_property_delivery_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Summary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - total_records: Annotated[int, Field(description='Total number of records validated')] - total_impressions: Annotated[int, Field(description='Total impressions across all records')] - compliant_records: Annotated[int, Field(description='Number of records with compliant status')] - compliant_impressions: Annotated[int, Field(description='Impressions from compliant records')] - non_compliant_records: Annotated[ - int, Field(description='Number of records with non_compliant status') - ] - non_compliant_impressions: Annotated[ - int, Field(description='Impressions from non_compliant records') - ] - not_covered_records: Annotated[ - int, - Field( - description='Number of records where identifier was recognized but no data available' - ), - ] - not_covered_impressions: Annotated[ - int, Field(description='Impressions from not_covered records') - ] - unidentified_records: Annotated[ - int, Field(description='Number of records where identifier type was not resolvable') - ] - unidentified_impressions: Annotated[ - int, Field(description='Impressions from unidentified records') - ] - - -class Aggregate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - score: Annotated[ - float | None, Field(description='Numeric score (0-100 scale typical, but agent-defined)') - ] = None - grade: Annotated[ - str | None, - Field(description="Letter grade or category (e.g., 'A+', 'B-', 'Gold', 'Compliant')"), - ] = None - label: Annotated[ - str | None, - Field(description="Human-readable summary (e.g., '85% compliant', 'High quality')"), - ] = None - methodology_url: Annotated[ - AnyUrl | None, Field(description='URL explaining how this aggregate was calculated') - ] = None - - -class AuthorizationSummary(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - records_checked: Annotated[ - int, Field(description='Number of records with sales_agent_url provided') - ] - impressions_checked: Annotated[ - int, Field(description='Total impressions from records with sales_agent_url') - ] - authorized_records: Annotated[ - int, Field(description='Number of records where sales agent was authorized') - ] - authorized_impressions: Annotated[int, Field(description='Impressions from authorized records')] - unauthorized_records: Annotated[ - int, Field(description='Number of records where sales agent was NOT authorized') - ] - unauthorized_impressions: Annotated[ - int, Field(description='Impressions from unauthorized records') - ] - unknown_records: Annotated[ - int, - Field( - description='Number of records where authorization could not be determined (adagents.json unavailable)' - ), - ] - unknown_impressions: Annotated[ - int, - Field(description='Impressions from records where authorization could not be determined'), - ] - - -class Type(StrEnum): - domain = 'domain' - subdomain = 'subdomain' - network_id = 'network_id' - ios_bundle = 'ios_bundle' - android_package = 'android_package' - apple_app_store_id = 'apple_app_store_id' - google_play_id = 'google_play_id' - roku_store_id = 'roku_store_id' - fire_tv_asin = 'fire_tv_asin' - samsung_app_id = 'samsung_app_id' - apple_tv_bundle = 'apple_tv_bundle' - bundle_id = 'bundle_id' - venue_id = 'venue_id' - screen_id = 'screen_id' - openooh_venue_type = 'openooh_venue_type' - rss_url = 'rss_url' - apple_podcast_id = 'apple_podcast_id' - spotify_collection_id = 'spotify_collection_id' - podcast_guid = 'podcast_guid' - station_id = 'station_id' - facility_id = 'facility_id' - - -class Identifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[ - Type, - Field( - description='Type of identifier', - examples=[ - 'domain', - 'ios_bundle', - 'venue_id', - 'apple_podcast_id', - 'station_id', - 'facility_id', - ], - title='Property Identifier Types', - ), - ] - value: Annotated[ - str, - Field( - description="The identifier value. For domain type: 'example.com' matches base domain plus www and m subdomains; 'edition.example.com' matches that specific subdomain; '*.example.com' matches ALL subdomains but NOT base domain" - ), - ] - - -class Status338(StrEnum): - compliant = 'compliant' - non_compliant = 'non_compliant' - not_covered = 'not_covered' - unidentified = 'unidentified' - - -class Status339(StrEnum): - passed = 'passed' - failed = 'failed' - warning = 'warning' - unevaluated = 'unevaluated' - - -class Requirement(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min_value: Annotated[float | None, Field(description='Minimum value that was required')] = None - max_value: Annotated[float | None, Field(description='Maximum value that was allowed')] = None - allowed_values: Annotated[ - list[Any] | None, Field(description='Values that would have been acceptable') - ] = None - - -class Feature(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="Which feature was evaluated. Data features come from the governance agent's feature catalog (e.g., 'mfa_score', 'carbon_score'). Record-level structural checks use reserved namespaces: 'record:list_membership', 'record:excluded', 'delivery:seller_authorization', 'delivery:click_url_presence'. Reserved prefixes: 'record:', 'delivery:'." - ), - ] - status: Annotated[ - Status339, - Field( - description='Per-feature evaluation outcome in content standards checks. For the two-outcome overall record verdict, see binary-verdict.', - title='Feature Check Status', - ), - ] - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Property-list agents populate when the validation was motivated by a specific policy. See /docs/governance/policy-attribution.' - ), - ] = None - explanation: Annotated[ - str | None, Field(description='Directional human-readable explanation of the result.') - ] = None - requirement: Annotated[ - Requirement | None, - Field( - description='The feature requirement that was not met. MAY be present on failed features when the caller authored the requirement (e.g., feature_requirements on a property list). The buyer set these thresholds — echoing them back enables fix-and-retry loops without looking up the list definition.' - ), - ] = None - confidence: Annotated[ - float | None, - Field( - description='Optional evaluator confidence in this result (0-1). Distinguishes certain verdicts from ambiguous ones.', - ge=0.0, - le=1.0, - ), - ] = None - - -class Status340(StrEnum): - authorized = 'authorized' - unauthorized = 'unauthorized' - unknown = 'unknown' - - -class Violation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - code: Annotated[str, Field(description='Machine-readable violation code')] - message: Annotated[str, Field(description='Human-readable violation description')] - - -class Authorization(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - status: Annotated[ - Status340, - Field( - description='Authorization status: authorized (agent in adagents.json), unauthorized (agent not in adagents.json), unknown (could not fetch or parse adagents.json)' - ), - ] - publisher_domain: Annotated[ - str | None, Field(description='The publisher domain where adagents.json was checked') - ] = None - sales_agent_url: Annotated[ - AnyUrl | None, Field(description='The sales agent URL that was validated') - ] = None - violation: Annotated[ - Violation | None, - Field( - description='Details about the authorization failure (only present for unauthorized status)' - ), - ] = None - - -class Result(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - identifier: Annotated[ - Identifier, Field(description='The identifier that was validated', title='Identifier') - ] - record_id: Annotated[ - str | None, Field(description='Client-provided ID from the delivery record (if provided)') - ] = None - status: Annotated[ - Status338, - Field( - description='Validation status: compliant (in list), non_compliant (not in list), not_covered (identifier recognized but no data available), unidentified (identifier type not resolvable by this governance agent)' - ), - ] - impressions: Annotated[int, Field(description='Number of impressions from this record', ge=0)] - features: Annotated[ - list[Feature] | None, - Field( - description='Per-feature breakdown for this record. SHOULD include all failed and warning features. MAY include passed features. For property validation the buyer authored the requirement, so the `requirement` that was not met MAY be echoed back on failures — this is contract data, not evaluator IP.' - ), - ] = None - authorization: Annotated[ - Authorization | None, - Field( - description='Authorization validation result (only present if sales_agent_url was provided in the delivery record)', - title='Authorization Result', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class ValidatePropertyDeliveryResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - compliant: Annotated[ - bool | None, - Field( - description='Overall compliance flag for the submitted delivery — true when every record is compliant, false when any record is non_compliant. Derived from summary.non_compliant_records === 0 but surfaced at the root as a convenience signal for buyers. Agents MAY omit this field when the response represents a partial validation (e.g., when include_compliant is false and results only contains non_compliant records); consumers SHOULD fall back to summary counts if compliant is absent.' - ), - ] = None - list_id: Annotated[str, Field(description='ID of the property list validated against')] - summary: Annotated[Summary, Field(description='Aggregate validation statistics')] - aggregate: Annotated[ - Aggregate | None, - Field( - description='Optional aggregate measurements computed by the governance agent. Format and meaning are agent-specific.' - ), - ] = None - authorization_summary: Annotated[ - AuthorizationSummary | None, - Field( - description='Aggregate authorization statistics. Only present if any records included sales_agent_url.' - ), - ] = None - results: Annotated[ - list[Result], - Field( - description='Per-record validation results. By default only includes non_compliant and unknown records. Set include_compliant=true to include all records.' - ), - ] - validated_at: Annotated[ - AwareDatetime, Field(description='Timestamp when validation was performed') - ] - list_resolved_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp of the property list resolution used for validation'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_request.py b/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_request.py deleted file mode 100644 index 5505f4d6..00000000 --- a/src/adcp/types/generated_poc/bundled/protocol/get_adcp_capabilities_request.py +++ /dev/null @@ -1,62 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/protocol/get_adcp_capabilities_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Protocol(StrEnum): - media_buy = 'media_buy' - signals = 'signals' - governance = 'governance' - sponsored_intelligence = 'sponsored_intelligence' - creative = 'creative' - - -class GetAdcpCapabilitiesRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - protocols: Annotated[ - list[Protocol] | None, - Field( - description='Specific protocols to query capabilities for. If omitted, returns capabilities for all supported protocols.', - min_length=1, - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/protocol/get_task_status_request.py b/src/adcp/types/generated_poc/bundled/protocol/get_task_status_request.py deleted file mode 100644 index d76234d3..00000000 --- a/src/adcp/types/generated_poc/bundled/protocol/get_task_status_request.py +++ /dev/null @@ -1,630 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/protocol/get_task_status_request.json -# timestamp: 2026-06-05T16:54:33+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent437(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent437 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account25(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class GetTaskStatusRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - task_id: Annotated[str, Field(description='Unique identifier of the task to retrieve')] - account: Annotated[ - Account | Account25 | None, - Field( - description='Account scope for the task lookup. Sellers MUST return REFERENCE_NOT_FOUND for a task_id that exists only under a different account or principal. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - include_history: Annotated[ - bool | None, - Field( - description='Include full conversation history for this task (may increase response size)' - ), - ] = False - include_result: Annotated[ - bool | None, - Field( - description="Include the task's result payload when status is completed. Defaults to false for lightweight status-only polls. When true, sellers MUST include result on the response when status is completed." - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/protocol/get_task_status_response.py b/src/adcp/types/generated_poc/bundled/protocol/get_task_status_response.py deleted file mode 100644 index d14f2689..00000000 --- a/src/adcp/types/generated_poc/bundled/protocol/get_task_status_response.py +++ /dev/null @@ -1,126459 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/protocol/get_task_status_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from enum import IntEnum -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel, StringConstraints - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Progress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - - -class Type(StrEnum): - request = 'request' - response = 'response' - - -class HistoryItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - timestamp: Annotated[AwareDatetime, Field(description='When this exchange occurred (ISO 8601)')] - type: Annotated[ - Type, Field(description='Whether this was a request from client or response from server') - ] - data: Annotated[dict[str, Any], Field(description='The full request or response payload')] - - -class Issue17(Issue): - pass - - -class AdcpError13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue17] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class PublisherDomain(RootModel[str]): - root: Annotated[ - str, Field(pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$') - ] - - -class PublisherProperty1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same selector across many publishers (e.g., a managed network listing every publisher it represents). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all properties from each addressed publisher are included' - ), - ] = 'all' - - -class PropertyId(RootModel[str]): - root: Annotated[ - str, - Field( - description='Identifier for a publisher property. Must be lowercase alphanumeric with underscores only.', - examples=['cnn_ctv_app', 'homepage', 'mobile_ios', 'instagram'], - pattern='^[a-z0-9_]+$', - title='Property ID', - ), - ] - - -class PublisherProperty2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com').", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific property IDs'), - ] = 'by_id' - property_ids: Annotated[ - list[PropertyId], - Field(description="Specific property IDs from the publisher's adagents.json", min_length=1), - ] - - -class PropertyTag(RootModel[str]): - root: Annotated[ - str, - Field( - description='Tag for categorizing publisher properties. Must be lowercase alphanumeric with underscores only.', - examples=['ctv', 'premium', 'news', 'sports', 'meta_network', 'social_media'], - pattern='^[a-z0-9_]+$', - title='Property Tag', - ), - ] - - -class PublisherProperty3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where publisher's adagents.json is hosted (e.g., 'cnn.com'). XOR with `publisher_domains` — exactly one MUST be present on each `publisher_properties[]` entry; both-present and neither-present both fail validation.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - publisher_domains: Annotated[ - list[PublisherDomain] | None, - Field( - description="Compact form for fanning the same tag predicate across many publishers (canonical managed-network shape). Each entry is the domain where that publisher's adagents.json is hosted. Each listed domain MUST be canonicalized to lowercase (the `pattern` already rejects uppercase). Mutually exclusive with `publisher_domain`. Each listed domain counts as explicitly scoped for the `managerdomain` fallback safety rule.", - min_length=1, - ), - ] = None - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by property tags') - ] = 'by_tag' - property_tags: Annotated[ - list[PropertyTag], - Field( - description="Property tags resolved against each addressed publisher's adagents.json, OR against the parent file's top-level `properties[]` when those properties carry a `publisher_domain` matching the selector. Selector covers all properties carrying any of these tags.", - min_length=1, - ), - ] - - -class PublisherProperty4(AdCPBaseModel): - pass - - -class PublisherProperty5(PublisherProperty1, PublisherProperty4): - pass - - -class PublisherProperty6(PublisherProperty2, PublisherProperty4): - pass - - -class PublisherProperty7(PublisherProperty3, PublisherProperty4): - pass - - -class PublisherProperty(RootModel[PublisherProperty5 | PublisherProperty6 | PublisherProperty7]): - root: PublisherProperty5 | PublisherProperty6 | PublisherProperty7 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class FormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class SellerPreference(StrEnum): - preferred = 'preferred' - accepted = 'accepted' - discouraged = 'discouraged' - - -class V1FormatRefItem(FormatId): - pass - - -class FormatSchema(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - uri: Annotated[ - AnyUrl, - Field( - description="HTTPS URL identifying the extension. `https://` is mandatory — `http://`, `file://`, `data:`, and other schemes are rejected at the schema layer (defense-in-depth on top of the fetch-contract normative rules). The URI base is the owning agent's URL; the path identifies the extension within that agent. Example: 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel'. The full fetch contract — SSRF allowlist, response-size cap, $ref sandbox, schema-compile bounds — is documented on `product-format-declaration.json#format_schema` and applies to ALL fetches of this reference shape regardless of whether the field is named `format_schema` (load-bearing for validation) or `platform_extensions` (informational); the *transport* rules are identical, only the *consumption* semantics differ." - ), - ] - digest: Annotated[ - str, - Field( - description='SHA-256 content digest of the extension definition (sha256:). Used to detect drift — if the agent revises the extension, the digest changes and cached definitions become invalid.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] - - -class CompositionModel(StrEnum): - deterministic = 'deterministic' - algorithmic = 'algorithmic' - - -class PlatformExtension(FormatSchema): - pass - - -class AssetType(StrEnum): - image = 'image' - video = 'video' - audio = 'audio' - text = 'text' - markdown = 'markdown' - url = 'url' - html = 'html' - css = 'css' - javascript = 'javascript' - vast = 'vast' - daast = 'daast' - webhook = 'webhook' - brief = 'brief' - catalog = 'catalog' - published_post = 'published_post' - zip = 'zip' - card = 'card' - object = 'object' - pixel_tracker = 'pixel_tracker' - vast_tracker = 'vast_tracker' - daast_tracker = 'daast_tracker' - - -class ConnectionType(StrEnum): - advertiser_account = 'advertiser_account' - publisher_identity = 'publisher_identity' - post_authorization = 'post_authorization' - - -class RequiredForItem(RootModel[str]): - root: Annotated[ - str, - Field( - examples=[ - 'list_creatives', - 'sync_creatives', - 'create_media_buy', - 'get_media_buy_delivery', - 'get_creative_delivery', - ], - min_length=1, - ), - ] - - -class Scope(StrEnum): - account = 'account' - identity = 'identity' - post = 'post' - unknown = 'unknown' - - -class Status(StrEnum): - connected = 'connected' - missing = 'missing' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ResourceRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - platform_account_id: Annotated[ - str | None, - Field( - description='Provider-native advertiser or business account id, when safe to disclose.' - ), - ] = None - identity_id: Annotated[ - str | None, - Field( - description='Provider-native creator, page, channel, organization, or profile id, when safe to disclose.' - ), - ] = None - handle: Annotated[ - str | None, - Field(description='Provider-native public handle for the owning identity, when available.'), - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the owning identity, when available.') - ] = None - post_id: Annotated[ - str | None, - Field( - description='Provider-native post id, when the grant is post-scoped or the failed request referenced a specific post.' - ), - ] = None - post_url: Annotated[ - AnyUrl | None, Field(description='Public URL for the referenced post, when available.') - ] = None - - -class RequiredConnection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, - Field( - description='Stable provider or platform namespace, preferably lowercase. Examples: `social.example`, `shortvideo.example`, or a seller-defined namespace. Omit only when the requirement is provider-agnostic, or when an `authorization_url` fully routes the human to the correct provider-specific connection flow.' - ), - ] = None - connection_type: Annotated[ - ConnectionType, - Field( - description='Kind of downstream connection required. `advertiser_account` is the platform account used to buy/manage ads. `publisher_identity` is the creator, page, channel, organization, or profile that owns source posts. `post_authorization` is a post-scoped grant when the platform authorizes individual posts instead of, or in addition to, the owning identity.' - ), - ] - required_for: Annotated[ - list[RequiredForItem] | None, - Field( - description='Concrete AdCP protocol operation names that require this downstream connection. Sellers SHOULD include this in product declarations when the requirement is known ahead of time, and in AUTHORIZATION_REQUIRED details when it explains the failed operation. Prefer specific operation names such as `list_creatives`, `sync_creatives`, `create_media_buy`, `get_media_buy_delivery`, or `get_creative_delivery` over broad category labels such as `reporting`.' - ), - ] = None - scope: Annotated[Scope | None, Field(description='Granularity of the downstream grant.')] = None - status: Annotated[ - Status | None, - Field( - description='Current seller-observed state for this downstream connection when known. Product declarations MAY omit status or use `unknown`; AUTHORIZATION_REQUIRED details SHOULD use `missing`, `expired`, or `revoked` for the connection that blocked the call.' - ), - ] = None - connection_id: Annotated[ - str | None, - Field( - description='Seller-defined identifier for an already-created downstream connection. Omit when no connection exists yet or when exposing it would leak platform/account state.' - ), - ] = None - resource_ref: Annotated[ - ResourceRef | None, - Field( - description='Optional opaque provider-native resource hint, such as a platform account id, profile URL, handle, channel id, post id, or post URL. This is a hint for routing authorization, not proof that authorization exists.' - ), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field( - description='Seller-hosted or provider-hosted URL where a human can complete or restore this downstream connection.' - ), - ] = None - authorization_instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for completing or restoring this downstream connection.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='Expiration time for the downstream grant, when known.'), - ] = None - - -class ReferenceMutability(StrEnum): - immutable_snapshot = 'immutable_snapshot' - mutable_requires_reapproval = 'mutable_requires_reapproval' - mutable_auto_recheck = 'mutable_auto_recheck' - - -class Size(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - width: Annotated[int, Field(ge=1)] - height: Annotated[int, Field(ge=1)] - - -class ImageFormat(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - svg = 'svg' - - -class AssetSource(StrEnum): - buyer_uploaded = 'buyer_uploaded' - publisher_host_recorded = 'publisher_host_recorded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class BuyerAssetAcceptance(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class RequiredConnection13(RequiredConnection): - pass - - -class MraidVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - - -class ClicktagMacro(StrEnum): - clickTag = 'clickTag' - clickTAG = 'clickTAG' - - -class RequiredConnection14(RequiredConnection): - pass - - -class SupportedTagType(StrEnum): - iframe = 'iframe' - javascript = 'javascript' - field_1x1_redirect = '1x1_redirect' - - -class RequiredConnection15(RequiredConnection): - pass - - -class AllowedCardMediaAssetType(StrEnum): - image = 'image' - video = 'video' - - -class RequiredConnection16(RequiredConnection): - pass - - -class Orientation(StrEnum): - vertical = 'vertical' - horizontal = 'horizontal' - square = 'square' - - -class DurationMsRange(RootModel[int]): - root: Annotated[int, Field(ge=0)] - - -class VideoCodec(StrEnum): - h264 = 'h264' - h265 = 'h265' - vp8 = 'vp8' - vp9 = 'vp9' - av1 = 'av1' - prores = 'prores' - - -class AudioCodec(StrEnum): - aac = 'aac' - mp3 = 'mp3' - opus = 'opus' - pcm = 'pcm' - - -class Container(StrEnum): - mp4 = 'mp4' - webm = 'webm' - mov = 'mov' - - -class Captions(StrEnum): - required = 'required' - recommended = 'recommended' - not_required = 'not_required' - - -class CompanionBannerWidth(RootModel[int]): - root: Annotated[int, Field(ge=1)] - - -class CompanionBannerHeight(CompanionBannerWidth): - pass - - -class RequiredConnection17(RequiredConnection): - pass - - -class VastVersion(StrEnum): - field_2_0 = '2.0' - field_3_0 = '3.0' - field_4_0 = '4.0' - field_4_1 = '4.1' - field_4_2 = '4.2' - - -class VpaidVersion(StrEnum): - field_1_0 = '1.0' - field_2_0 = '2.0' - - -class DurationMsRangeItem(DurationMsRange): - pass - - -class RequiredConnection18(RequiredConnection): - pass - - -class AudioCodec4(StrEnum): - mp3 = 'mp3' - aac = 'aac' - wav = 'wav' - opus = 'opus' - flac = 'flac' - - -class AudioSampleRate(CompanionBannerWidth): - pass - - -class AudioChannel(StrEnum): - mono = 'mono' - stereo = 'stereo' - - -class RequiredConnection19(RequiredConnection): - pass - - -class DaastVersion(StrEnum): - field_1_0 = '1.0' - field_1_1 = '1.1' - - -class RequiredConnection20(RequiredConnection): - pass - - -class SupportedCatalogType(StrEnum): - product = 'product' - store = 'store' - offering = 'offering' - hotel = 'hotel' - flight = 'flight' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - job = 'job' - inventory = 'inventory' - - -class FanoutMode(StrEnum): - per_item = 'per_item' - multi_item_in_creative = 'multi_item_in_creative' - single_item = 'single_item' - - -class SupportedIdType(StrEnum): - asin = 'asin' - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - store_id = 'store_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - job_id = 'job_id' - - -class ItemProductionModel(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - - -class RequiredConnection21(RequiredConnection): - pass - - -class MainImageSize(Size): - pass - - -class IconSize(Size): - pass - - -class ImageFormat3(StrEnum): - jpg = 'jpg' - jpeg = 'jpeg' - png = 'png' - gif = 'gif' - webp = 'webp' - - -class AssetSource9(StrEnum): - buyer_uploaded = 'buyer_uploaded' - seller_pre_rendered_from_brief = 'seller_pre_rendered_from_brief' - seller_human_designed = 'seller_human_designed' - agent_synthesized = 'agent_synthesized' - publisher_owned_reference = 'publisher_owned_reference' - - -class RequiredConnection22(RequiredConnection): - pass - - -class RequiredConnection23(RequiredConnection): - pass - - -class OutputModality(StrEnum): - text = 'text' - audio = 'audio' - card = 'card' - - -class Kind(StrEnum): - publisher_ref = 'publisher_ref' - seller_inline = 'seller_inline' - - -class Mode(StrEnum): - targetable = 'targetable' - included = 'included' - - -class RequiredConnection24(RequiredConnection): - pass - - -class RequiredConnection25(RequiredConnection): - pass - - -class RequiredConnection26(RequiredConnection): - pass - - -class RequiredConnection27(RequiredConnection): - pass - - -class RequiredConnection28(RequiredConnection): - pass - - -class RequiredConnection29(RequiredConnection): - pass - - -class RequiredConnection30(RequiredConnection): - pass - - -class RequiredConnection31(RequiredConnection): - pass - - -class RequiredConnection32(RequiredConnection): - pass - - -class RequiredConnection33(RequiredConnection): - pass - - -class RequiredConnection34(RequiredConnection): - pass - - -class RequiredConnection35(RequiredConnection): - pass - - -class PriceGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - p25: Annotated[ - float | None, Field(description='25th percentile of recent winning bids', ge=0.0) - ] = None - p50: Annotated[float | None, Field(description='Median of recent winning bids', ge=0.0)] = None - p75: Annotated[ - float | None, Field(description='75th percentile of recent winning bids', ge=0.0) - ] = None - p90: Annotated[ - float | None, Field(description='90th percentile of recent winning bids', ge=0.0) - ] = None - - -class ViewThreshold(RootModel[float]): - root: Annotated[ - float, - Field( - description='Percentage completion threshold (0.0 to 1.0, e.g., 0.5 = 50%)', - ge=0.0, - le=1.0, - ), - ] - - -class ViewThreshold3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - duration_seconds: Annotated[int, Field(description='Seconds of viewing required', ge=1)] - - -class Parameters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold | ViewThreshold3 - - -class Parameters5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['dooh'], Field(description='Discriminator identifying this as DOOH parameters') - ] = 'dooh' - sov_percentage: Annotated[ - float | None, - Field(description='Guaranteed share of voice as a percentage (0-100)', ge=0.0, le=100.0), - ] = None - loop_duration_seconds: Annotated[ - int | None, Field(description='Duration of the ad loop rotation in seconds', ge=1) - ] = None - min_plays_per_hour: Annotated[ - int | None, Field(description='Minimum number of plays per hour guaranteed', ge=1) - ] = None - venue_package: Annotated[ - str | None, Field(description='Named collection of screens included in this buy') - ] = None - duration_hours: Annotated[ - float | None, - Field( - description='Duration of the DOOH slot in hours (e.g., 24 for a full-day takeover)', - ge=0.0, - ), - ] = None - daypart: Annotated[ - str | None, - Field(description='Named daypart for this slot (e.g., morning_commute, evening_rush)'), - ] = None - estimated_impressions: Annotated[ - int | None, - Field( - description='Estimated audience impressions for this slot (informational, not a delivery guarantee)', - ge=0, - ), - ] = None - - -class TimeUnit(StrEnum): - hour = 'hour' - day = 'day' - week = 'week' - month = 'month' - - -class Parameters6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - time_unit: Annotated[ - TimeUnit, - Field( - description='The time unit for pricing. Total cost = fixed_price × number of time_units in the campaign flight.' - ), - ] - min_duration: Annotated[ - int | None, Field(description='Minimum booking duration in time_units', ge=1) - ] = None - max_duration: Annotated[ - int | None, - Field( - description='Maximum booking duration in time_units. Must be >= min_duration when both are present.', - ge=1, - ), - ] = None - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class Dimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' - placement_ref: Annotated[ - PlacementRef, - Field( - description="Structured placement reference for this forecast row. References an entry from the product's placements array.", - title='Placement Reference', - ), - ] - placement_name: Annotated[ - str | None, - Field( - description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' - ), - ] = None - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Presence(StrEnum): - present = 'present' - absent = 'absent' - - -class Dimensions13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class AudienceSize(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) - ] = None - - -class Reach(AudienceSize): - pass - - -class Frequency(AudienceSize): - pass - - -class Impressions(AudienceSize): - pass - - -class Clicks(AudienceSize): - pass - - -class Spend(AudienceSize): - pass - - -class Views(AudienceSize): - pass - - -class CompletedViews(AudienceSize): - pass - - -class Grps(AudienceSize): - pass - - -class Engagements(AudienceSize): - pass - - -class Follows(AudienceSize): - pass - - -class Saves(AudienceSize): - pass - - -class ProfileVisits(AudienceSize): - pass - - -class MeasuredImpressions(AudienceSize): - pass - - -class Downloads(AudienceSize): - pass - - -class Plays(AudienceSize): - pass - - -class CoverageRate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0) - ] = None - - -class Metrics(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate | None, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent439(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class MeasurableImpressions(AudienceSize): - pass - - -class ViewableImpressions(AudienceSize): - pass - - -class ViewableRate(CoverageRate): - pass - - -class ViewedSeconds(AudienceSize): - pass - - -class DeclaredBy220(DeclaredBy): - pass - - -class VerifyAgent440(VerifyAgent): - pass - - -class VerifyAgent441(VerifyAgent439): - pass - - -class VerificationItem220(VerificationItem): - pass - - -class Value(AudienceSize): - pass - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class Window(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class OutcomeMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy221(DeclaredBy): - pass - - -class VerifyAgent442(VerifyAgent): - pass - - -class VerifyAgent443(VerifyAgent439): - pass - - -class VerificationItem221(VerificationItem): - pass - - -class DeclaredBy222(DeclaredBy): - pass - - -class VerifyAgent444(VerifyAgent): - pass - - -class VerifyAgent445(VerifyAgent439): - pass - - -class VerificationItem222(VerificationItem): - pass - - -class DeclaredBy223(DeclaredBy): - pass - - -class VerifyAgent446(VerifyAgent): - pass - - -class VerifyAgent447(VerifyAgent439): - pass - - -class VerificationItem223(VerificationItem): - pass - - -class NoticePeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class Type23(StrEnum): - percent_remaining = 'percent_remaining' - full_commitment = 'full_commitment' - fixed_fee = 'fixed_fee' - none = 'none' - - -class CancellationFee(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type23, - Field( - description="Fee calculation method. 'percent_remaining': percentage of remaining uncommitted spend. 'full_commitment': buyer owes the full committed budget regardless of delivery. 'fixed_fee': flat monetary amount. 'none': no financial fee (cancellation with notice is free)." - ), - ] - rate: Annotated[ - float | None, - Field( - description="Fee rate as a decimal proportion of remaining committed spend. Required when type is 'percent_remaining' (e.g., 0.5 means 50% of remaining spend).", - ge=0.0, - le=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Fixed fee amount in the buy's currency. Required when type is 'fixed_fee'.", - ge=0.0, - ), - ] = None - - -class CancellationPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee, Field(description='Fee applied when the notice period is not met.') - ] - - -class Sla(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - regex_engine="python-re", - ) - response_max: Annotated[ - str | None, - Field( - description='Maximum time from when the buyer issues the action to when the seller acknowledges receipt (mode-appropriate: synchronous response for self_serve, tolerance decision for conditional_self_serve, or queue ack for requires_approval). ISO 8601 duration.', - examples=['PT5M', 'PT4H', 'P1D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - completion_max: Annotated[ - str | None, - Field( - description='Maximum time from buyer issuing the action to the seller completing it (mutation applied, proposal finalized, approval resolved). ISO 8601 duration.', - examples=['PT1H', 'PT24H', 'P2D'], - pattern='^P(?!$)(\\d+Y)?(\\d+M)?(\\d+D)?(T(\\d+H)?(\\d+M)?(\\d+S)?)?$', - ), - ] = None - - -class DeclaredBy224(DeclaredBy): - pass - - -class VerifyAgent448(VerifyAgent): - pass - - -class VerifyAgent449(VerifyAgent439): - pass - - -class VerificationItem224(VerificationItem): - pass - - -class ME(StrEnum): - zip = 'zip' - zip_plus_four = 'zip_plus_four' - - -class GBEnum(StrEnum): - outward = 'outward' - full = 'full' - - -class CAEnum(StrEnum): - fsa = 'fsa' - full = 'full' - - -class PostalArea(AdCPBaseModel): - US: Annotated[list[ME] | None, Field(min_length=1)] = None - GB: Annotated[list[GBEnum] | None, Field(min_length=1)] = None - CA: Annotated[list[CAEnum] | None, Field(min_length=1)] = None - DE: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - CH: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - AT: Annotated[list[Literal['plz']] | None, Field(min_length=1)] = None - FR: Annotated[list[Literal['code_postal']] | None, Field(min_length=1)] = None - AU: Annotated[list[Literal['postcode']] | None, Field(min_length=1)] = None - BR: Annotated[list[Literal['cep']] | None, Field(min_length=1)] = None - IN: Annotated[list[Literal['pin']] | None, Field(min_length=1)] = None - ZA: Annotated[list[Literal['postal_code']] | None, Field(min_length=1)] = None - us_zip: Annotated[bool | None, Field(deprecated=True)] = None - us_zip_plus_four: Annotated[bool | None, Field(deprecated=True)] = None - gb_outward: Annotated[bool | None, Field(deprecated=True)] = None - gb_full: Annotated[bool | None, Field(deprecated=True)] = None - ca_fsa: Annotated[bool | None, Field(deprecated=True)] = None - ca_full: Annotated[bool | None, Field(deprecated=True)] = None - de_plz: Annotated[bool | None, Field(deprecated=True)] = None - fr_code_postal: Annotated[bool | None, Field(deprecated=True)] = None - au_postcode: Annotated[bool | None, Field(deprecated=True)] = None - ch_plz: Annotated[bool | None, Field(deprecated=True)] = None - at_plz: Annotated[bool | None, Field(deprecated=True)] = None - - -class SupportsGeoBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - -class DateRangeSupport(StrEnum): - date_range = 'date_range' - lifetime_only = 'lifetime_only' - - -class MeasurementWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - window_id: Annotated[ - str, - Field( - description="Identifier for this maturation stage. Standard broadcast values: 'live' (real-time viewers only), 'c3' (live + 3 days time-shifted), 'c7' (live + 7 days time-shifted). Standard values for other channels include 'tentative' (provisional data available quickly), 'final' (post-processing certified data), 'post_ivt' (digital after invalid-traffic filtering), 'post_sivt' (digital after sophisticated-IVT filtering), 'downloads_7d' / 'downloads_30d' (podcast download maturation). Sellers may define custom IDs.", - examples=['live', 'c3', 'c7', 'tentative', 'final', 'post_ivt', 'downloads_30d'], - max_length=50, - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of what this window measures', - examples=[ - 'Live broadcast impressions only', - 'Live plus 7 days of time-shifted viewing', - 'Tentative plays before IVT and fraud-check processing', - 'Final plays after IVT and fraud-check processing', - 'Impressions after sophisticated invalid-traffic filtering', - ], - max_length=500, - ), - ] = None - duration_days: Annotated[ - int, - Field( - description='Number of days of accumulation included in this window before processing begins. For broadcast, this is DVR accumulation (0 = live only, 3 = live + 3 days DVR, 7 = live + 7 days DVR). For channels without an accumulation period (DOOH tentative→final, digital IVT filtering), this is 0 — maturation is entirely vendor processing time captured in expected_availability_days.', - ge=0, - ), - ] - expected_availability_days: Annotated[ - int | None, - Field( - description="Expected number of days after delivery before this window's data is available from the measurement vendor. Captures accumulation time plus vendor processing time. Examples: broadcast C7 from VideoAmp ~22 days (7-day accumulation + ~15-day processing); DOOH tentative plays same-day; DOOH final (post-IVT/fraud-check) ~1 day; digital post-SIVT ~2–3 days.", - ge=0, - ), - ] = None - is_guarantee_basis: Annotated[ - bool | None, - Field( - description="Whether this window is the basis for delivery guarantees, reconciliation, and invoicing. A product typically has one guarantee basis window (e.g., C7 for most US broadcast, post-IVT final for DOOH). Buyers reconcile against the guarantee basis window's final numbers." - ), - ] = None - - -class ProvenanceRequirements(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - require_digital_source_type: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `digital_source_type` field in their provenance, set to a valid value from the `digital-source-type` enum (not null or absent). Submissions that omit this field are rejected with `PROVENANCE_DIGITAL_SOURCE_TYPE_MISSING`. Supports EU AI Act Art. 50 and CA SB 942 compliance workflows where AI disclosure metadata must be present at the protocol level.' - ), - ] = None - require_disclosure_metadata: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include a `disclosure` object in their provenance with `disclosure.required` set to a boolean value (true or false). When `disclosure.required` is true, at least one entry in `disclosure.jurisdictions` is expected. Submissions that omit `disclosure.required` are rejected with `PROVENANCE_DISCLOSURE_MISSING`.' - ), - ] = None - require_embedded_provenance: Annotated[ - bool | None, - Field( - description='When true, the seller requires creatives to include at least one `embedded_provenance` entry. For pipelines where sidecar metadata is stripped by intermediaries, this ensures provenance data persists through delivery. Submissions that omit `embedded_provenance` are rejected with `PROVENANCE_EMBEDDED_MISSING`.' - ), - ] = None - - -class AcceptedVerifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent. MUST use the `https://` scheme. The seller calls this URL via `get_creative_features` to verify a buyer's claim; the seller has already vetted the endpoint and accepts responsibility for outbound calls to it." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional canonical `feature_id` the seller will request against this agent (e.g., `encypher.markers_present_v2`). When present, the buyer's `verify_agent.feature_id` SHOULD either match this value or be omitted. When absent, the seller selects a feature from the agent's `governance.creative_features` catalog at evaluation time. Resolves the selector ambiguity that would otherwise let two compliant receivers reach different verdicts." - ), - ] = None - providers: Annotated[ - list[str] | None, - Field( - description="Optional `provider` labels this agent verifies (e.g., `['Encypher', 'Digimarc']`). When present, sellers SHOULD only invoke this agent for `embedded_provenance[]` / `watermarks[]` entries whose `provider` field matches one of these labels — letting buyers pre-flight whether their attached evidence is verifiable against the seller's allowlist. When absent, the agent is treated as provider-agnostic.", - min_length=1, - ), - ] = None - - - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[float, Field(description='Minimum value, inclusive.')] - max: Annotated[float, Field(description='Maximum value, inclusive.')] - - - - - - -class ActivationStatus(StrEnum): - ready = 'ready' - requires_activation = 'requires_activation' - - -class AllowedTargetingMode(StrEnum): - include = 'include' - exclude = 'exclude' - - -class AppliesToOutputFormatId(FormatId): - pass - - -class PricingOption141(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption146(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption147(PricingOption141, PricingOption146): - pass - - -class PricingOption148(PricingOption142, PricingOption146): - pass - - -class PricingOption149(PricingOption143, PricingOption146): - pass - - -class PricingOption1410(PricingOption144, PricingOption146): - pass - - -class PricingOption1411(PricingOption145, PricingOption146): - pass - - -class PricingOption( - RootModel[ - PricingOption147 - | PricingOption148 - | PricingOption149 - | PricingOption1410 - | PricingOption1411 - ] -): - root: Annotated[ - PricingOption147 - | PricingOption148 - | PricingOption149 - | PricingOption1410 - | PricingOption1411, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class ResolutionModel(StrEnum): - direct_targeting = 'direct_targeting' - seller_planned = 'seller_planned' - - -class SelectionMode(StrEnum): - optional = 'optional' - required = 'required' - fixed = 'fixed' - - -class SelectionGroupRule(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - selection_group: Annotated[ - str, - Field( - description='ProductSignalTargetingOption.selection_group value this rule applies to.' - ), - ] - targeting_mode: Annotated[ - AllowedTargetingMode | None, - Field( - description="How options in this selection_group are intended to be used in signal_targeting_groups. 'include' maps to child groups with operator 'any'. 'exclude' maps to child groups with operator 'none'. Omit when options in the group may be used according to each option's allowed_targeting_modes." - ), - ] = None - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Selection behavior for this selection_group. 'required' means at least min_selected_signals, or 1 when omitted. 'fixed' means default_selected options in this group are seller-applied and read-only." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum selected options from this selection_group. If selection_mode is 'required' and omitted, sellers MUST treat the minimum as 1.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, Field(description='Maximum selected options from this selection_group.', ge=1) - ] = None - - -class SignalTargetingRules(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class SupportedMetric(StrEnum): - clicks = 'clicks' - views = 'views' - completed_views = 'completed_views' - viewed_seconds = 'viewed_seconds' - attention_seconds = 'attention_seconds' - attention_score = 'attention_score' - engagements = 'engagements' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - reach = 'reach' - - -class SupportedViewDuration(RootModel[float]): - root: Annotated[float, Field(gt=0.0)] - - -class SupportedTarget(StrEnum): - cost_per = 'cost_per' - threshold_rate = 'threshold_rate' - - -class DeclaredBy225(DeclaredBy): - pass - - -class VerifyAgent450(VerifyAgent): - pass - - -class VerifyAgent451(VerifyAgent439): - pass - - -class VerificationItem225(VerificationItem): - pass - - -class Severity(StrEnum): - error = 'error' - warning = 'warning' - info = 'info' - - -class Issue18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - severity: Annotated[ - Severity, - Field( - description="'error': blocks optimization until resolved. 'warning': optimization works but effectiveness is reduced. 'info': suggestion for improvement." - ), - ] - message: Annotated[ - str, - Field(description='Human/agent-readable description of the issue and how to resolve it.'), - ] - - -class SupportedTarget6(StrEnum): - cost_per = 'cost_per' - per_ad_spend = 'per_ad_spend' - maximize_value = 'maximize_value' - - -class MatchedGtin(RootModel[str]): - root: Annotated[str, Field(pattern='^[0-9]{8,14}$')] - - -class CatalogMatch(AdCPBaseModel): - matched_gtins: Annotated[ - list[MatchedGtin] | None, - Field( - description="GTINs from the buyer's catalog that are eligible on this product's inventory. Standard GTIN formats (GTIN-8 through GTIN-14). Only present for product-type catalogs with GTIN matching." - ), - ] = None - matched_ids: Annotated[ - list[str] | None, - Field( - description="Item IDs from the buyer's catalog that matched this product's inventory. The ID type depends on the catalog type and content_id_type (e.g., SKUs for product catalogs, job_ids for job catalogs, offering_ids for offering catalogs)." - ), - ] = None - matched_count: Annotated[ - int | None, - Field(description="Number of catalog items that matched this product's inventory.", ge=0), - ] = None - submitted_count: Annotated[ - int, Field(description="Total catalog items evaluated from the buyer's catalog.", ge=0) - ] - - -class DeclaredBy226(DeclaredBy): - pass - - -class VerifyAgent452(VerifyAgent): - pass - - -class VerifyAgent453(VerifyAgent439): - pass - - -class VerificationItem226(VerificationItem): - pass - - -class DeclaredBy227(DeclaredBy): - pass - - -class VerifyAgent454(VerifyAgent): - pass - - -class VerifyAgent455(VerifyAgent439): - pass - - -class VerificationItem227(VerificationItem): - pass - - -class DeclaredBy228(DeclaredBy): - pass - - -class VerifyAgent456(VerifyAgent): - pass - - -class VerifyAgent457(VerifyAgent439): - pass - - -class VerificationItem228(VerificationItem): - pass - - -class Specification(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[str, Field(max_length=60)] - value: Annotated[str, Field(max_length=200)] - - -class Collection(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str, - Field( - description="Domain where the adagents.json declaring these collections is hosted (e.g., 'mrbeast.com'). The collections array in that file contains the authoritative collection definitions.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - collection_ids: Annotated[ - list[str], - Field( - description='Collection IDs from the adagents.json collections array. Each ID must match a collection_id declared in that file.', - min_length=1, - ), - ] - - -class AdInventory(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - expected_breaks: Annotated[ - int, Field(description='Number of planned ad breaks in the installment', ge=0) - ] - total_ad_seconds: Annotated[ - int | None, Field(description='Total seconds of ad time across all breaks', ge=0) - ] = None - max_ad_duration_seconds: Annotated[ - int | None, - Field( - description='Maximum duration in seconds for a single ad within a break. Buyers need this to know whether their creative fits.', - ge=1, - ), - ] = None - unplanned_breaks: Annotated[ - bool | None, - Field( - description='Whether ad breaks are dynamic and driven by live conditions (sports timeouts, election coverage). When false, all breaks are pre-defined.' - ), - ] = None - supported_formats: Annotated[ - list[str] | None, - Field( - description="Ad format types supported in breaks (e.g., 'video', 'audio', 'display')" - ), - ] = None - - -class MaterialDeadline(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - stage: Annotated[ - str, - Field( - description="Submission stage identifier. Use 'draft' for materials that need seller processing and 'final' for production-ready assets. Sellers may define additional stages.", - examples=['draft', 'final'], - ), - ] - due_at: Annotated[ - AwareDatetime, Field(description='When materials for this stage are due (ISO 8601)') - ] - label: Annotated[ - str | None, - Field( - description="What the seller needs at this stage (e.g., 'Talking points and brand guidelines', 'Press-ready PDF with bleed')" - ), - ] = None - - -class Deadlines(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - booking_deadline: Annotated[ - AwareDatetime | None, - Field( - description='Last date/time to book a placement in this installment (ISO 8601). After this point, the seller will not accept new bookings.' - ), - ] = None - cancellation_deadline: Annotated[ - AwareDatetime | None, - Field( - description="Last date/time to cancel without penalty (ISO 8601). Cancellations after this point may incur fees per the seller's terms." - ), - ] = None - material_deadlines: Annotated[ - list[MaterialDeadline] | None, - Field( - description="Stages for creative material submission. Items MUST be in chronological order by due_at (earliest first). Typical pattern: 'draft' for raw materials the seller will process, 'final' for production-ready assets. Print example: draft artwork then press-ready PDF. Influencer example: talking points then approved script.", - min_length=1, - ), - ] = None - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class MaterialSubmission(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field(description='HTTPS URL for uploading or submitting physical creative materials'), - ] = None - email: Annotated[ - EmailStr | None, Field(description='Email address for creative material submission') - ] = None - instructions: Annotated[ - str | None, - Field( - description='Human-readable instructions for material submission (file naming conventions, shipping address, etc.)', - max_length=2000, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PublisherProperty81(PublisherProperty1): - pass - - -class PublisherProperty82(PublisherProperty2): - pass - - -class PublisherProperty83(PublisherProperty3): - pass - - -class PublisherProperty84(PublisherProperty4): - pass - - -class PublisherProperty85(PublisherProperty81, PublisherProperty84): - pass - - -class PublisherProperty86(PublisherProperty82, PublisherProperty84): - pass - - -class PublisherProperty87(PublisherProperty83, PublisherProperty84): - pass - - -class PublisherProperty8( - RootModel[PublisherProperty85 | PublisherProperty86 | PublisherProperty87] -): - root: PublisherProperty85 | PublisherProperty86 | PublisherProperty87 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection36(RequiredConnection): - pass - - -class RequiredConnection37(RequiredConnection): - pass - - -class RequiredConnection38(RequiredConnection): - pass - - -class RequiredConnection39(RequiredConnection): - pass - - -class RequiredConnection40(RequiredConnection): - pass - - -class RequiredConnection41(RequiredConnection): - pass - - -class RequiredConnection42(RequiredConnection): - pass - - -class RequiredConnection43(RequiredConnection): - pass - - -class RequiredConnection44(RequiredConnection): - pass - - -class RequiredConnection45(RequiredConnection): - pass - - -class RequiredConnection46(RequiredConnection): - pass - - -class RequiredConnection47(RequiredConnection): - pass - - -class RequiredConnection48(RequiredConnection): - pass - - -class RequiredConnection49(RequiredConnection): - pass - - -class RequiredConnection50(RequiredConnection): - pass - - -class RequiredConnection51(RequiredConnection): - pass - - -class RequiredConnection52(RequiredConnection): - pass - - -class RequiredConnection53(RequiredConnection): - pass - - -class RequiredConnection54(RequiredConnection): - pass - - -class RequiredConnection55(RequiredConnection): - pass - - -class RequiredConnection56(RequiredConnection): - pass - - -class RequiredConnection57(RequiredConnection): - pass - - -class RequiredConnection58(RequiredConnection): - pass - - -class RequiredConnection59(RequiredConnection): - pass - - -class ViewThreshold4(ViewThreshold): - pass - - -class ViewThreshold5(ViewThreshold3): - pass - - -class Parameters7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold4 | ViewThreshold5 - - -class Parameters9(Parameters5): - pass - - -class Parameters10(Parameters6): - pass - - -class Dimensions15(Dimensions): - pass - - - - -class Dimensions19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics3(Metrics): - pass - - -class DeclaredBy229(DeclaredBy): - pass - - -class VerifyAgent458(VerifyAgent): - pass - - -class VerifyAgent459(VerifyAgent439): - pass - - -class VerificationItem229(VerificationItem): - pass - - -class DeclaredBy230(DeclaredBy): - pass - - -class VerifyAgent460(VerifyAgent): - pass - - -class VerifyAgent461(VerifyAgent439): - pass - - -class VerificationItem230(VerificationItem): - pass - - -class Window2(Window): - pass - - -class OutcomeMeasurement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window2 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy231(DeclaredBy): - pass - - -class VerifyAgent462(VerifyAgent): - pass - - -class VerifyAgent463(VerifyAgent439): - pass - - -class VerificationItem231(VerificationItem): - pass - - -class DeclaredBy232(DeclaredBy): - pass - - -class VerifyAgent464(VerifyAgent): - pass - - -class VerifyAgent465(VerifyAgent439): - pass - - -class VerificationItem232(VerificationItem): - pass - - -class DeclaredBy233(DeclaredBy): - pass - - -class VerifyAgent466(VerifyAgent): - pass - - -class VerifyAgent467(VerifyAgent439): - pass - - -class VerificationItem233(VerificationItem): - pass - - -class NoticePeriod1(NoticePeriod): - pass - - -class CancellationFee2(CancellationFee): - pass - - -class CancellationPolicy2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod1, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee2, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy234(DeclaredBy): - pass - - -class VerifyAgent468(VerifyAgent): - pass - - -class VerifyAgent469(VerifyAgent439): - pass - - -class VerificationItem234(VerificationItem): - pass - - -class PostalArea1(PostalArea): - pass - - -class SupportsGeoBreakdown1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea1 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption151(PricingOption141): - pass - - -class PricingOption152(PricingOption142): - pass - - -class PricingOption153(PricingOption143): - pass - - -class PricingOption154(PricingOption144): - pass - - -class PricingOption155(PricingOption145): - pass - - -class PricingOption156(PricingOption146): - pass - - -class PricingOption157(PricingOption151, PricingOption156): - pass - - -class PricingOption158(PricingOption152, PricingOption156): - pass - - -class PricingOption159(PricingOption153, PricingOption156): - pass - - -class PricingOption1510(PricingOption154, PricingOption156): - pass - - -class PricingOption1511(PricingOption155, PricingOption156): - pass - - -class PricingOption15( - RootModel[ - PricingOption157 - | PricingOption158 - | PricingOption159 - | PricingOption1510 - | PricingOption1511 - ] -): - root: Annotated[ - PricingOption157 - | PricingOption158 - | PricingOption159 - | PricingOption1510 - | PricingOption1511, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule1(SelectionGroupRule): - pass - - -class SignalTargetingRules1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule1] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy235(DeclaredBy): - pass - - -class VerifyAgent470(VerifyAgent): - pass - - -class VerifyAgent471(VerifyAgent439): - pass - - -class VerificationItem235(VerificationItem): - pass - - -class Issue19(Issue18): - pass - - -class CatalogMatch1(CatalogMatch): - pass - - -class DeclaredBy236(DeclaredBy): - pass - - -class VerifyAgent472(VerifyAgent): - pass - - -class VerifyAgent473(VerifyAgent439): - pass - - -class VerificationItem236(VerificationItem): - pass - - -class DeclaredBy237(DeclaredBy): - pass - - -class VerifyAgent474(VerifyAgent): - pass - - -class VerifyAgent475(VerifyAgent439): - pass - - -class VerificationItem237(VerificationItem): - pass - - -class DeclaredBy238(DeclaredBy): - pass - - -class VerifyAgent476(VerifyAgent): - pass - - -class VerifyAgent477(VerifyAgent439): - pass - - -class VerificationItem238(VerificationItem): - pass - - -class Deadlines1(Deadlines): - pass - - -class Extensions(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - extends: Annotated[ - str, - Field( - description='Canonical concept this extension extends (e.g., `tracking`, `cta_vocabulary`, `destinations`, `placement`).' - ), - ] - fields: Annotated[ - dict[str, Any], - Field( - description='JSON Schema fragment declaring the additional fields this extension contributes.' - ), - ] - version: Annotated[ - str | None, - Field( - description='Semantic version of the extension definition. Distinct from the digest — version is human-readable; digest is the integrity check.' - ), - ] = None - description: str | None = None - - -class Dimensions21(Dimensions): - pass - - - - -class Dimensions25(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics4(Metrics): - pass - - -class DeclaredBy239(DeclaredBy): - pass - - -class VerifyAgent478(VerifyAgent): - pass - - -class VerifyAgent479(VerifyAgent439): - pass - - -class VerificationItem239(VerificationItem): - pass - - -class DeclaredBy240(DeclaredBy): - pass - - -class VerifyAgent480(VerifyAgent): - pass - - -class VerifyAgent481(VerifyAgent439): - pass - - -class VerificationItem240(VerificationItem): - pass - - -class ProposalStatus(StrEnum): - draft = 'draft' - committed = 'committed' - - -class TotalBudget(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[ - str, Field(description='ISO 4217 currency code', max_length=3, min_length=3) - ] - - -class PaymentTerms1(StrEnum): - net_30 = 'net_30' - net_60 = 'net_60' - net_90 = 'net_90' - prepaid = 'prepaid' - due_on_receipt = 'due_on_receipt' - - -class Terms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - advertiser: Annotated[ - str | None, Field(description='Advertiser name or identifier', max_length=500) - ] = None - publisher: Annotated[ - str | None, Field(description='Publisher name or identifier', max_length=500) - ] = None - total_budget: Annotated[TotalBudget | None, Field(description='Total committed budget')] = None - flight_start: Annotated[AwareDatetime | None, Field(description='Campaign start date')] = None - flight_end: Annotated[AwareDatetime | None, Field(description='Campaign end date')] = None - payment_terms: Annotated[PaymentTerms1 | None, Field(description='Payment terms')] = None - - -class InsertionOrder(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - io_id: Annotated[ - str, - Field( - description='Unique identifier for this insertion order. Referenced by io_acceptance on create_media_buy.', - max_length=255, - ), - ] - terms: Annotated[ - Terms | None, - Field( - description='Summary fields echoed from the committed proposal for agent verification. Buyer agents use these to confirm the IO matches what was negotiated before a human signs. These are read-only summaries, not negotiation surfaces — deal terms live on products and packages.' - ), - ] = None - terms_url: Annotated[ - AnyUrl | None, - Field( - description='URL to a human-readable document containing the full insertion order terms' - ), - ] = None - signing_url: Annotated[ - AnyUrl | None, - Field( - description='URL to an electronic signing service (e.g., DocuSign) for human signature workflows. When present, a human must sign before the buyer agent can proceed with create_media_buy.' - ), - ] = None - requires_signature: Annotated[ - bool, - Field( - description='Whether the buyer must accept this IO before creating a media buy. When true, create_media_buy requires an io_acceptance referencing this io_id.' - ), - ] - - -class TotalBudgetGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[float | None, Field(description='Minimum recommended budget', ge=0.0)] = None - recommended: Annotated[ - float | None, Field(description='Recommended budget for optimal performance', ge=0.0) - ] = None - max: Annotated[ - float | None, Field(description='Maximum budget before diminishing returns', ge=0.0) - ] = None - currency: Annotated[str | None, Field(description='ISO 4217 currency code')] = None - - -class Dimensions27(Dimensions): - pass - - - - -class Dimensions31(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics5(Metrics): - pass - - -class DeclaredBy241(DeclaredBy): - pass - - -class VerifyAgent482(VerifyAgent): - pass - - -class VerifyAgent483(VerifyAgent439): - pass - - -class VerificationItem241(VerificationItem): - pass - - -class DeclaredBy242(DeclaredBy): - pass - - -class VerifyAgent484(VerifyAgent): - pass - - -class VerifyAgent485(VerifyAgent439): - pass - - -class VerificationItem242(VerificationItem): - pass - - -class Issue20(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue20] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status90(StrEnum): - applied = 'applied' - partial = 'partial' - unable = 'unable' - - -class RefinementApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['request'], - Field(description="Echoes scope 'request' from the corresponding refine entry."), - ] = 'request' - status: Annotated[ - Status90, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['product'], - Field(description="Echoes scope 'product' from the corresponding refine entry."), - ] = 'product' - product_id: Annotated[ - str, Field(description='Echoes product_id from the corresponding refine entry.') - ] - status: Annotated[ - Status90, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['proposal'], - Field(description="Echoes scope 'proposal' from the corresponding refine entry."), - ] = 'proposal' - proposal_id: Annotated[ - str, Field(description='Echoes proposal_id from the corresponding refine entry.') - ] - status: Annotated[ - Status90, - Field( - description="'applied': the ask was fulfilled. 'partial': the ask was partially fulfilled — see notes for details. 'unable': the seller could not fulfill the ask — see notes for why." - ), - ] - notes: Annotated[ - str | None, - Field( - description="Seller explanation of what was done, what couldn't be done, or why. Recommended when status is 'partial' or 'unable'." - ), - ] = None - - -class RefinementApplied4(RootModel[RefinementApplied | RefinementApplied6 | RefinementApplied7]): - root: Annotated[ - RefinementApplied | RefinementApplied6 | RefinementApplied7, Field(discriminator='scope') - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Scope70(StrEnum): - products = 'products' - pricing = 'pricing' - forecast = 'forecast' - proposals = 'proposals' - wholesale_feed = 'wholesale_feed' - - -class EstimatedWait(Window): - pass - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope70, - Field( - description="'products': not all inventory sources were searched. 'pricing': products returned but pricing is absent or unconfirmed. 'forecast': products returned but forecast data is absent. 'proposals': proposals were not generated or are incomplete. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget — symmetric with get_signals' 'wholesale_feed' scope so sellers have a precise way to declare wholesale-incomplete on the products surface." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait | None, - Field( - description='How much additional time would resolve this scope. Allows the buyer to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class Semantics(StrEnum): - only = 'only' - any = 'any' - approximate = 'approximate' - - -class ExcludedBy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - count: Annotated[ - int, - Field( - description='Number of products excluded by this filter, interpreted per the parent `semantics` field.', - ge=0, - ), - ] - values: Annotated[ - list[str | dict[str, Any]] | None, - Field( - description='Optional list of the specific filter values that contributed to exclusions, when meaningful. For `required_metrics`: the metric names that excluded products (strings). For `required_vendor_metrics`: the vendor/metric pin entries (objects). Item shape is filter-specific; the schema admits string OR object items. Buyers without filter-specific knowledge SHOULD treat as opaque.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Optional human-readable note about why this filter narrowed the set (e.g., 'no products in this brief support DV viewability at the requested threshold')." - ), - ] = None - - -class FilterDiagnostics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - semantics: Annotated[ - Semantics | None, - Field( - description="How `excluded_by[*].count` values are computed across multiple filters. `only`: counts products that would have been included if not for THIS filter alone (deterministic; the right value for 'which filter killed my result set' triage — recommended when feasible). `any`: counts products excluded by ANY filter (so multiple filters' counts may overlap and sum to more than `total_candidates`). `approximate`: sellers SHOULD use this when their pipeline can't cleanly attribute exclusions to a single filter. Buyers SHOULD inspect `semantics` before doing arithmetic on counts." - ), - ] = None - total_candidates: Annotated[ - int | None, - Field( - description='Number of products the seller considered before applying `filters`. Baseline for interpreting per-filter exclusion counts. Approximate — sellers MAY return a sampled or capped count when their candidate pool is large. Optional; sellers whose baseline candidate set size is sensitive (revealing market posture or competitive density) MAY omit this while still emitting `excluded_by`.', - ge=0, - ), - ] = None - excluded_by: Annotated[ - dict[str, ExcludedBy] | None, - Field( - description="Per-filter exclusion counts, keyed by the filter property name as it appears in the request's `filters` object (e.g., `pricing_currencies`, `required_metrics`, `required_vendor_metrics`, `required_geo_targeting`, `budget_range`). Values are objects carrying `count` and optional filter-specific detail. Only filters that actually narrowed the set need appear here; absence of a key means that filter did not exclude anything (or was not in the request)." - ), - ] = None - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class Result244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, - Field(description='Progress percentage of the search operation', ge=0.0, le=100.0), - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step in the search process (e.g., 'searching_inventory', 'validating_availability')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the search process') - ] = None - step_number: Annotated[int | None, Field(description='Current step number (1-indexed)')] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason(StrEnum): - CLARIFICATION_NEEDED = 'CLARIFICATION_NEEDED' - BUDGET_REQUIRED = 'BUDGET_REQUIRED' - - -class PublisherProperty91(PublisherProperty1): - pass - - -class PublisherProperty92(PublisherProperty2): - pass - - -class PublisherProperty93(PublisherProperty3): - pass - - -class PublisherProperty94(PublisherProperty4): - pass - - -class PublisherProperty95(PublisherProperty91, PublisherProperty94): - pass - - -class PublisherProperty96(PublisherProperty92, PublisherProperty94): - pass - - -class PublisherProperty97(PublisherProperty93, PublisherProperty94): - pass - - -class PublisherProperty9( - RootModel[PublisherProperty95 | PublisherProperty96 | PublisherProperty97] -): - root: PublisherProperty95 | PublisherProperty96 | PublisherProperty97 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection60(RequiredConnection): - pass - - -class RequiredConnection61(RequiredConnection): - pass - - -class RequiredConnection62(RequiredConnection): - pass - - -class RequiredConnection63(RequiredConnection): - pass - - -class RequiredConnection64(RequiredConnection): - pass - - -class RequiredConnection65(RequiredConnection): - pass - - -class RequiredConnection66(RequiredConnection): - pass - - -class RequiredConnection67(RequiredConnection): - pass - - -class RequiredConnection68(RequiredConnection): - pass - - -class RequiredConnection69(RequiredConnection): - pass - - -class RequiredConnection70(RequiredConnection): - pass - - -class RequiredConnection71(RequiredConnection): - pass - - -class RequiredConnection72(RequiredConnection): - pass - - -class RequiredConnection73(RequiredConnection): - pass - - -class RequiredConnection74(RequiredConnection): - pass - - -class RequiredConnection75(RequiredConnection): - pass - - -class RequiredConnection76(RequiredConnection): - pass - - -class RequiredConnection77(RequiredConnection): - pass - - -class RequiredConnection78(RequiredConnection): - pass - - -class RequiredConnection79(RequiredConnection): - pass - - -class RequiredConnection80(RequiredConnection): - pass - - -class RequiredConnection81(RequiredConnection): - pass - - -class RequiredConnection82(RequiredConnection): - pass - - -class RequiredConnection83(RequiredConnection): - pass - - -class ViewThreshold6(ViewThreshold): - pass - - -class ViewThreshold7(ViewThreshold3): - pass - - -class Parameters11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold6 | ViewThreshold7 - - -class Parameters13(Parameters5): - pass - - -class Parameters14(Parameters6): - pass - - -class Dimensions33(Dimensions): - pass - - - - -class Dimensions37(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics6(Metrics): - pass - - -class DeclaredBy243(DeclaredBy): - pass - - -class VerifyAgent486(VerifyAgent): - pass - - -class VerifyAgent487(VerifyAgent439): - pass - - -class VerificationItem243(VerificationItem): - pass - - -class DeclaredBy244(DeclaredBy): - pass - - -class VerifyAgent488(VerifyAgent): - pass - - -class VerifyAgent489(VerifyAgent439): - pass - - -class VerificationItem244(VerificationItem): - pass - - -class Window3(Window): - pass - - -class OutcomeMeasurement2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window3 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy245(DeclaredBy): - pass - - -class VerifyAgent490(VerifyAgent): - pass - - -class VerifyAgent491(VerifyAgent439): - pass - - -class VerificationItem245(VerificationItem): - pass - - -class DeclaredBy246(DeclaredBy): - pass - - -class VerifyAgent492(VerifyAgent): - pass - - -class VerifyAgent493(VerifyAgent439): - pass - - -class VerificationItem246(VerificationItem): - pass - - -class DeclaredBy247(DeclaredBy): - pass - - -class VerifyAgent494(VerifyAgent): - pass - - -class VerifyAgent495(VerifyAgent439): - pass - - -class VerificationItem247(VerificationItem): - pass - - -class NoticePeriod2(NoticePeriod): - pass - - -class CancellationFee3(CancellationFee): - pass - - -class CancellationPolicy3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod2, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee3, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy248(DeclaredBy): - pass - - -class VerifyAgent496(VerifyAgent): - pass - - -class VerifyAgent497(VerifyAgent439): - pass - - -class VerificationItem248(VerificationItem): - pass - - -class PostalArea2(PostalArea): - pass - - -class SupportsGeoBreakdown2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea2 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption161(PricingOption141): - pass - - -class PricingOption162(PricingOption142): - pass - - -class PricingOption163(PricingOption143): - pass - - -class PricingOption164(PricingOption144): - pass - - -class PricingOption165(PricingOption145): - pass - - -class PricingOption166(PricingOption146): - pass - - -class PricingOption167(PricingOption161, PricingOption166): - pass - - -class PricingOption168(PricingOption162, PricingOption166): - pass - - -class PricingOption169(PricingOption163, PricingOption166): - pass - - -class PricingOption1610(PricingOption164, PricingOption166): - pass - - -class PricingOption1611(PricingOption165, PricingOption166): - pass - - -class PricingOption16( - RootModel[ - PricingOption167 - | PricingOption168 - | PricingOption169 - | PricingOption1610 - | PricingOption1611 - ] -): - root: Annotated[ - PricingOption167 - | PricingOption168 - | PricingOption169 - | PricingOption1610 - | PricingOption1611, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule2(SelectionGroupRule): - pass - - -class SignalTargetingRules2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule2] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy249(DeclaredBy): - pass - - -class VerifyAgent498(VerifyAgent): - pass - - -class VerifyAgent499(VerifyAgent439): - pass - - -class VerificationItem249(VerificationItem): - pass - - -class Issue21(Issue18): - pass - - -class CatalogMatch2(CatalogMatch): - pass - - -class DeclaredBy250(DeclaredBy): - pass - - -class VerifyAgent500(VerifyAgent): - pass - - -class VerifyAgent501(VerifyAgent439): - pass - - -class VerificationItem250(VerificationItem): - pass - - -class DeclaredBy251(DeclaredBy): - pass - - -class VerifyAgent502(VerifyAgent): - pass - - -class VerifyAgent503(VerifyAgent439): - pass - - -class VerificationItem251(VerificationItem): - pass - - -class DeclaredBy252(DeclaredBy): - pass - - -class VerifyAgent504(VerifyAgent): - pass - - -class VerifyAgent505(VerifyAgent439): - pass - - -class VerificationItem252(VerificationItem): - pass - - -class Deadlines2(Deadlines): - pass - - -class PublisherProperty101(PublisherProperty1): - pass - - -class PublisherProperty102(PublisherProperty2): - pass - - -class PublisherProperty103(PublisherProperty3): - pass - - -class PublisherProperty104(PublisherProperty4): - pass - - -class PublisherProperty105(PublisherProperty101, PublisherProperty104): - pass - - -class PublisherProperty106(PublisherProperty102, PublisherProperty104): - pass - - -class PublisherProperty107(PublisherProperty103, PublisherProperty104): - pass - - -class PublisherProperty10( - RootModel[PublisherProperty105 | PublisherProperty106 | PublisherProperty107] -): - root: PublisherProperty105 | PublisherProperty106 | PublisherProperty107 - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RequiredConnection84(RequiredConnection): - pass - - -class RequiredConnection85(RequiredConnection): - pass - - -class RequiredConnection86(RequiredConnection): - pass - - -class RequiredConnection87(RequiredConnection): - pass - - -class RequiredConnection88(RequiredConnection): - pass - - -class RequiredConnection89(RequiredConnection): - pass - - -class RequiredConnection90(RequiredConnection): - pass - - -class RequiredConnection91(RequiredConnection): - pass - - -class RequiredConnection92(RequiredConnection): - pass - - -class RequiredConnection93(RequiredConnection): - pass - - -class RequiredConnection94(RequiredConnection): - pass - - -class RequiredConnection95(RequiredConnection): - pass - - -class RequiredConnection96(RequiredConnection): - pass - - -class RequiredConnection97(RequiredConnection): - pass - - -class RequiredConnection98(RequiredConnection): - pass - - -class RequiredConnection99(RequiredConnection): - pass - - -class RequiredConnection100(RequiredConnection): - pass - - -class RequiredConnection101(RequiredConnection): - pass - - -class RequiredConnection102(RequiredConnection): - pass - - -class RequiredConnection103(RequiredConnection): - pass - - -class RequiredConnection104(RequiredConnection): - pass - - -class RequiredConnection105(RequiredConnection): - pass - - -class RequiredConnection106(RequiredConnection): - pass - - -class RequiredConnection107(RequiredConnection): - pass - - -class ViewThreshold8(ViewThreshold): - pass - - -class ViewThreshold9(ViewThreshold3): - pass - - -class Parameters15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - view_threshold: ViewThreshold8 | ViewThreshold9 - - -class Parameters17(Parameters5): - pass - - -class Parameters18(Parameters6): - pass - - -class Dimensions39(Dimensions): - pass - - - - -class Dimensions43(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics7(Metrics): - pass - - -class DeclaredBy253(DeclaredBy): - pass - - -class VerifyAgent506(VerifyAgent): - pass - - -class VerifyAgent507(VerifyAgent439): - pass - - -class VerificationItem253(VerificationItem): - pass - - -class DeclaredBy254(DeclaredBy): - pass - - -class VerifyAgent508(VerifyAgent): - pass - - -class VerifyAgent509(VerifyAgent439): - pass - - -class VerificationItem254(VerificationItem): - pass - - -class Window4(Window): - pass - - -class OutcomeMeasurement3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - str, - Field( - description='Type of measurement', - examples=['incremental_sales_lift', 'brand_lift', 'foot_traffic'], - ), - ] - attribution: Annotated[ - str, - Field( - description='Attribution methodology', - examples=['deterministic_purchase', 'probabilistic'], - ), - ] - window: Annotated[ - Window4 | None, - Field( - description='Attribution window as a structured duration (e.g., {"interval": 30, "unit": "days"}).' - ), - ] = None - reporting: Annotated[ - str, - Field( - description='Reporting frequency and format', - examples=['weekly_dashboard', 'real_time_api'], - ), - ] - - -class DeclaredBy255(DeclaredBy): - pass - - -class VerifyAgent510(VerifyAgent): - pass - - -class VerifyAgent511(VerifyAgent439): - pass - - -class VerificationItem255(VerificationItem): - pass - - -class DeclaredBy256(DeclaredBy): - pass - - -class VerifyAgent512(VerifyAgent): - pass - - -class VerifyAgent513(VerifyAgent439): - pass - - -class VerificationItem256(VerificationItem): - pass - - -class DeclaredBy257(DeclaredBy): - pass - - -class VerifyAgent514(VerifyAgent): - pass - - -class VerifyAgent515(VerifyAgent439): - pass - - -class VerificationItem257(VerificationItem): - pass - - -class NoticePeriod3(NoticePeriod): - pass - - -class CancellationFee4(CancellationFee): - pass - - -class CancellationPolicy4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notice_period: Annotated[ - NoticePeriod3, - Field( - description="Minimum notice period before cancellation takes effect (e.g., { interval: 30, unit: 'days' }). A guaranteed buy canceled without sufficient notice incurs the declared cancellation fee.", - title='Duration', - ), - ] - cancellation_fee: Annotated[ - CancellationFee4, Field(description='Fee applied when the notice period is not met.') - ] - - -class DeclaredBy258(DeclaredBy): - pass - - -class VerifyAgent516(VerifyAgent): - pass - - -class VerifyAgent517(VerifyAgent439): - pass - - -class VerificationItem258(VerificationItem): - pass - - -class PostalArea3(PostalArea): - pass - - -class SupportsGeoBreakdown3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - bool | None, Field(description='Supports country-level geo breakdown (ISO 3166-1 alpha-2)') - ] = None - region: Annotated[ - bool | None, Field(description='Supports region/state-level geo breakdown (ISO 3166-2)') - ] = None - metro: Annotated[ - dict[MetroAreaSystem, bool] | None, - Field( - description='Metro area breakdown support. Keys are metro-system enum values; true means supported.' - ), - ] = None - postal_area: Annotated[ - PostalArea3 | None, - Field( - description='Declares supported postal area systems. Native support is keyed by ISO 3166-1 alpha-2 country with arrays of country-local postal systems. Deprecated country-fused boolean aliases may be included during migration.', - title='Postal Area Support', - ), - ] = None - - - - - - - - - - - -class PricingOption171(PricingOption141): - pass - - -class PricingOption172(PricingOption142): - pass - - -class PricingOption173(PricingOption143): - pass - - -class PricingOption174(PricingOption144): - pass - - -class PricingOption175(PricingOption145): - pass - - -class PricingOption176(PricingOption146): - pass - - -class PricingOption177(PricingOption171, PricingOption176): - pass - - -class PricingOption178(PricingOption172, PricingOption176): - pass - - -class PricingOption179(PricingOption173, PricingOption176): - pass - - -class PricingOption1710(PricingOption174, PricingOption176): - pass - - -class PricingOption1711(PricingOption175, PricingOption176): - pass - - -class PricingOption17( - RootModel[ - PricingOption177 - | PricingOption178 - | PricingOption179 - | PricingOption1710 - | PricingOption1711 - ] -): - root: Annotated[ - PricingOption177 - | PricingOption178 - | PricingOption179 - | PricingOption1710 - | PricingOption1711, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SelectionGroupRule3(SelectionGroupRule): - pass - - -class SignalTargetingRules3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - resolution_model: Annotated[ - ResolutionModel | None, - Field( - description="How selected signal_targeting_options are resolved against the product's inventory. 'direct_targeting' means selected signals are applied as targeting predicates to the package inventory. 'seller_planned' means selected signals are planning inputs that the seller resolves against product-specific inventory, timing, availability, reach, or pacing constraints; buyers SHOULD NOT attempt to decompose the signal selection into lower-level inventory or schedule decisions. Use 'seller_planned' for products such as linear broadcast schedules where the audience definition may be portable but the audience-to-avails plan is seller-resolved." - ), - ] = ResolutionModel.direct_targeting - selection_mode: Annotated[ - SelectionMode | None, - Field( - description="Default selection behavior for selectable signals on this product. 'optional' means the buyer may select zero or more signals. 'required' means the buyer must select at least min_selected_signals, or 1 when min_selected_signals is omitted. 'fixed' means the seller applies the default_selected signals and the buyer cannot add or remove them; buyers SHOULD render those entries as read-only and sellers MUST echo them in package targeting_overlay.signal_targeting_groups. Use selection_group_rules for product-scoped products that need different behavior for different groups, such as fixed suppressions plus a required include tier." - ), - ] = SelectionMode.optional - min_selected_signals: Annotated[ - int | None, - Field( - description="Minimum number of signals the buyer must select when selection_mode is 'required'. If selection_mode is 'required' and this field is omitted, sellers MUST treat the minimum as 1. Defaults to 0 for optional selection.", - ge=0, - ), - ] = None - max_selected_signals: Annotated[ - int | None, - Field( - description='Maximum number of signals the buyer may select for a package. Omit when there is no declared limit beyond the available options.', - ge=1, - ), - ] = None - max_selected_per_group: Annotated[ - int | None, - Field( - description='Maximum number of signal_targeting_options the buyer may select from the same ProductSignalTargetingOption.selection_group. Use 1 for mutually exclusive alternatives within each option group. This limit applies to product option grouping, not to the number of child groups in packages[].targeting_overlay.signal_targeting_groups.', - ge=1, - ), - ] = None - max_signal_targeting_groups: Annotated[ - int | None, - Field( - description='Maximum number of child groups allowed in packages[].targeting_overlay.signal_targeting_groups.groups. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - max_signals_per_targeting_group: Annotated[ - int | None, - Field( - description='Maximum number of signals allowed in each packages[].targeting_overlay.signal_targeting_groups.groups[].signals array. Omit when the seller has no declared limit beyond product terms.', - ge=1, - ), - ] = None - selection_group_rules: Annotated[ - list[SelectionGroupRule3] | None, - Field( - description='Optional product-scoped overrides for specific ProductSignalTargetingOption.selection_group values. Use this when one product has mixed behavior, such as fixed seller-applied suppressions, a required pick-one include tier, optional buyer-selected exclusions, or heterogeneous targeting planes that must be represented as separate ANDed clauses. Rules apply only to options whose selection_group matches. When selection_group_rules are present, each packages[].targeting_overlay.signal_targeting_groups child group MUST contain signals from exactly one selection_group and one targeting_mode, and buyers MUST send at most one child group for each (selection_group, targeting_mode) pair. Sellers MUST reject duplicate, mixed, or collapsed groups that combine distinct selection_group_rules into the same child group.', - min_length=1, - ), - ] = None - - -class DeclaredBy259(DeclaredBy): - pass - - -class VerifyAgent518(VerifyAgent): - pass - - -class VerifyAgent519(VerifyAgent439): - pass - - -class VerificationItem259(VerificationItem): - pass - - -class Issue22(Issue18): - pass - - -class CatalogMatch3(CatalogMatch): - pass - - -class DeclaredBy260(DeclaredBy): - pass - - -class VerifyAgent520(VerifyAgent): - pass - - -class VerifyAgent521(VerifyAgent439): - pass - - -class VerificationItem260(VerificationItem): - pass - - -class DeclaredBy261(DeclaredBy): - pass - - -class VerifyAgent522(VerifyAgent): - pass - - -class VerifyAgent523(VerifyAgent439): - pass - - -class VerificationItem261(VerificationItem): - pass - - -class DeclaredBy262(DeclaredBy): - pass - - -class VerifyAgent524(VerifyAgent): - pass - - -class VerifyAgent525(VerifyAgent439): - pass - - -class VerificationItem262(VerificationItem): - pass - - -class Deadlines3(Deadlines): - pass - - -class Issue23(Issue): - pass - - -class Error7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue23] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose products array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The products array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Custom curation queued; typical turnaround 10–30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, Field(description='Estimated completion time for the search') - ] = None - errors: Annotated[ - list[Error7] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue24(Issue): - pass - - -class AdcpError14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue24] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - - - - - - - - - -class SignalType(StrEnum): - marketplace = 'marketplace' - custom = 'custom' - owned = 'owned' - - -class Dimensions45(Dimensions): - pass - - - - -class Dimensions49(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class Metrics8(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] - - -class DeclaredBy263(DeclaredBy): - pass - - -class VerifyAgent526(VerifyAgent): - pass - - -class VerifyAgent527(VerifyAgent439): - pass - - -class VerificationItem263(VerificationItem): - pass - - -class DeclaredBy264(DeclaredBy): - pass - - -class VerifyAgent528(VerifyAgent): - pass - - -class VerifyAgent529(VerifyAgent439): - pass - - -class VerificationItem264(VerificationItem): - pass - - -class Kind12(StrEnum): - inventory = 'inventory' - product = 'product' - account = 'account' - custom = 'custom' - - -class DateRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[date_aliased, Field(description='Start date (inclusive), ISO 8601')] - end: Annotated[date_aliased, Field(description='End date (inclusive), ISO 8601')] - - -class Scope119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[Kind12, Field(description='Denominator family for the coverage forecast.')] - label: Annotated[ - str, - Field( - description="Human-readable denominator label, such as 'network price-priority inventory'." - ), - ] - product_id: Annotated[ - str | None, Field(description="Product denominator when kind is 'product'.") - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries included in the denominator, as ISO 3166-1 alpha-2 codes.', - min_length=1, - ), - ] = None - line_item_types: Annotated[ - list[str] | None, - Field( - description='Seller or ad-server line item types included in the denominator.', - min_length=1, - ), - ] = None - date_range: Annotated[ - DateRange | None, - Field( - description='Historical or planned date window used to compute the denominator.', - title='Date Range', - ), - ] = None - - -class BucketSemantics(StrEnum): - exclusive = 'exclusive' - overlapping = 'overlapping' - - -class BucketCompleteness(StrEnum): - complete = 'complete' - partial = 'partial' - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Deployments(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[str, Field(description='Platform identifier for DSPs')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - - -class Deployments5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - -class Deployments3(RootModel[Deployments | Deployments5]): - root: Annotated[ - Deployments | Deployments5, - Field( - description='A signal deployment to a specific deployment target with activation status and key', - discriminator='type', - title='Deployment', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class PricingOption181(PricingOption141): - pass - - -class PricingOption182(PricingOption142): - pass - - -class PricingOption183(PricingOption143): - pass - - -class PricingOption184(PricingOption144): - pass - - -class PricingOption185(PricingOption145): - pass - - -class PricingOption186(PricingOption146): - pass - - -class PricingOption187(PricingOption181, PricingOption186): - pass - - -class PricingOption188(PricingOption182, PricingOption186): - pass - - -class PricingOption189(PricingOption183, PricingOption186): - pass - - -class PricingOption1810(PricingOption184, PricingOption186): - pass - - -class PricingOption1811(PricingOption185, PricingOption186): - pass - - -class PricingOption18( - RootModel[ - PricingOption187 - | PricingOption188 - | PricingOption189 - | PricingOption1810 - | PricingOption1811 - ] -): - root: Annotated[ - PricingOption187 - | PricingOption188 - | PricingOption189 - | PricingOption1810 - | PricingOption1811, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RestrictedAttribute(StrEnum): - racial_ethnic_origin = 'racial_ethnic_origin' - political_opinions = 'political_opinions' - religious_beliefs = 'religious_beliefs' - trade_union_membership = 'trade_union_membership' - health_data = 'health_data' - sex_life_sexual_orientation = 'sex_life_sexual_orientation' - genetic_data = 'genetic_data' - biometric_data = 'biometric_data' - age = 'age' - familial_status = 'familial_status' - - -class Value10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(min_length=1)] - path: str | None = None - modifiers: list[str] | None = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: str - taxonomy_value_id: str - path: str | None = None - modifiers: list[str] | None = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: AnyUrl - version: str | None = None - segtax: Annotated[int | None, Field(ge=1)] = None - etag: str | None = None - values: Annotated[list[Value10], Field(min_length=1)] - value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None - parent_match_behavior: ParentMatchBehavior | None = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: bool | None = None - pre_onboarding_device_expansion: bool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class ConsentBasi(StrEnum): - consent = 'consent' - legitimate_interest = 'legitimate_interest' - contract = 'contract' - legal_obligation = 'legal_obligation' - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type27(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type27 - provider_signed: Annotated[ - bool, - Field( - description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Audience(StrEnum): - buyer = 'buyer' - data_subject = 'data_subject' - regulator = 'regulator' - public = 'public' - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - region: Annotated[ - str | None, - Field( - description='Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.' - ), - ] = None - regulation: Annotated[ - str, - Field(description='Provider-supplied regulation identifier for the disclosure obligation.'), - ] - disclosure_text: Annotated[ - str | None, - Field( - description='Human-readable disclosure text or summary the provider expects buyers or reviewers to see.' - ), - ] = None - disclosure_url: Annotated[ - AnyUrl | None, - Field( - description="Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction." - ), - ] = None - audience: Annotated[ - Audience | None, Field(description='Primary audience for this disclosure entry.') - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: Annotated[ - bool, - Field( - description="The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Jurisdictions where a modeling or AI-use disclosure applies.', min_length=1 - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.', - max_length=2000, - ), - ] = None - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method - seed_source: SeedSource - training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] - ai_act_risk_class: AiActRiskClass - disclosure: Annotated[ - Disclosure | None, - Field( - description='Disclosure requirements and jurisdictional notes for modeled data signals. This schema is intentionally separate from core/provenance.json because creative provenance is about generated content, render guidance, and asset-level chain of custody, while signal modeling disclosure is about data-segment methodology and data-use transparency.', - title='Signal Modeling Disclosure', - ), - ] = None - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[list[Right], Field(min_length=1)] - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - countries: list[Country] | None = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[list[Channel], Field(min_length=1)] - response_sla_days: Annotated[int | None, Field(ge=1, le=90)] = None - ccpa_opt_out_url: AnyUrl | None = None - - -class Issue25(Issue): - pass - - -class Error8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue25] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scope120(StrEnum): - signals = 'signals' - pricing = 'pricing' - wholesale_feed = 'wholesale_feed' - - -class EstimatedWait2(Window): - pass - - -class IncompleteItem4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope120, - Field( - description="'signals': not all matching signals were returned. 'pricing': signals returned but pricing is absent or unconfirmed. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait2 | None, - Field( - description='How much additional time would resolve this scope. Allows the caller to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class Result270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, - Field( - description='Progress percentage of the signal discovery operation.', ge=0.0, le=100.0 - ), - ] = None - current_step: Annotated[ - str | None, - Field( - description='Current step in the signal discovery process, such as `querying_providers`, `ranking_signals`, or `checking_deployments`.' - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the signal discovery process.') - ] = None - step_number: Annotated[int | None, Field(description='Current step number (1-indexed).')] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue26(Issue): - pass - - -class Error9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue26] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose signals array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the caller uses with tasks/get, and that the agent references on push-notification callbacks. The signals array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Provider discovery queued; typical turnaround 10-30 minutes.' Plain text only. Callers MUST treat this as untrusted agent input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile agent may inject prompt-injection payloads aimed at the caller's agent.", - max_length=2000, - ), - ] = None - estimated_completion: Annotated[ - AwareDatetime | None, - Field(description='Estimated completion time for the signal discovery task.'), - ] = None - errors: Annotated[ - list[Error9] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories or partial provider unavailability). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue27(Issue): - pass - - -class AdcpError15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue27] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class DeclaredBy265(DeclaredBy): - pass - - -class VerifyAgent530(VerifyAgent): - pass - - -class VerifyAgent531(VerifyAgent439): - pass - - -class VerificationItem265(VerificationItem): - pass - - -class Address(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - street: Annotated[ - str, Field(description='Street address including building number', max_length=200) - ] - city: Annotated[str, Field(max_length=100)] - postal_code: Annotated[str, Field(max_length=20)] - region: Annotated[ - str | None, Field(description='State, province, or region', max_length=100) - ] = None - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code', pattern='^[A-Z]{2}$') - ] - - -class Role279(StrEnum): - billing = 'billing' - legal = 'legal' - creative = 'creative' - general = 'general' - - -class Contact(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - role: Annotated[ - Role279, Field(description="Contact's functional role in the business relationship") - ] - name: Annotated[str | None, Field(description='Full name of the contact', max_length=200)] = ( - None - ) - email: Annotated[EmailStr | None, Field(max_length=254)] = None - phone: Annotated[str | None, Field(max_length=30)] = None - - -class Bank(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_holder: Annotated[str, Field(description='Name on the bank account', max_length=200)] - iban: Annotated[ - str | None, - Field( - description='International Bank Account Number (SEPA markets)', - pattern='^[A-Z]{2}[0-9]{2}[A-Z0-9]{4,30}$', - ), - ] = None - bic: Annotated[ - str | None, - Field( - description='Bank Identifier Code / SWIFT code (SEPA markets)', - pattern='^[A-Z]{4}[A-Z]{2}[A-Z0-9]{2}([A-Z0-9]{3})?$', - ), - ] = None - routing_number: Annotated[ - str | None, - Field( - description='Bank routing number for non-SEPA markets (e.g., US ABA routing number, Canadian transit/institution number)', - max_length=30, - ), - ] = None - account_number: Annotated[ - str | None, Field(description='Bank account number for non-SEPA markets', max_length=30) - ] = None - - -class BillingEntity(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreditLimit(AdCPBaseModel): - amount: Annotated[float, Field(ge=0.0)] - currency: Annotated[str, Field(pattern='^[A-Z]{3}$')] - - -class Setup(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl | None, - Field( - description='URL where the human can complete the required action (credit application, legal agreement, add funds).' - ), - ] = None - message: Annotated[str, Field(description="Human-readable description of what's needed.")] - expires_at: Annotated[ - AwareDatetime | None, Field(description='When this setup link expires.') - ] = None - - -class GovernanceAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: Annotated[AnyUrl, Field(description='Governance agent endpoint URL. Must use HTTPS.')] - - -class Format(StrEnum): - jsonl = 'jsonl' - csv = 'csv' - parquet = 'parquet' - avro = 'avro' - orc = 'orc' - - -class Compression(StrEnum): - gzip = 'gzip' - none = 'none' - - -class Contact10(Contact): - pass - - -class InvoiceRecipient(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact10] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Gtin(MatchedGtin): - pass - - -class Transform(StrEnum): - date = 'date' - divide = 'divide' - boolean = 'boolean' - split = 'split' # type: ignore[assignment] - - -class FeedFieldMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - feed_field: Annotated[ - str | None, - Field( - description='Field name in the external feed record. Omit when injecting a static literal value (use the value property instead).' - ), - ] = None - catalog_field: Annotated[ - str | None, - Field( - description="Target field on the catalog item schema, using dot notation for nested fields (e.g., 'name', 'price.amount', 'location.city'). Mutually exclusive with asset_group_id." - ), - ] = None - asset_group_id: Annotated[ - str | None, - Field( - description="Places the feed field value (a URL) into a typed asset pool on the catalog item's assets array. The value is wrapped as an image or video asset in a group with this ID. Use standard group IDs: 'images_landscape', 'images_vertical', 'images_square', 'logo', 'video'. Mutually exclusive with catalog_field." - ), - ] = None - value: Annotated[ - Any | None, - Field( - description='Static literal value to inject into catalog_field for every item, regardless of what the feed contains. Mutually exclusive with feed_field. Useful for fields the feed omits (e.g., currency when price is always USD, or a constant category value).' - ), - ] = None - transform: Annotated[ - Transform | None, - Field( - description='Named transform to apply to the feed field value before writing to the catalog schema. See transform-specific parameters (format, timezone, by, separator).' - ), - ] = None - format: Annotated[ - str | None, - Field( - description="For transform 'date': the input date format string (e.g., 'YYYYMMDD', 'MM/DD/YYYY', 'DD-MM-YYYY'). Output is always ISO 8601 (e.g., '2025-03-01'). Uses Unicode date pattern tokens." - ), - ] = None - timezone: Annotated[ - str | None, - Field( - description="For transform 'date': the timezone of the input value. IANA timezone identifier (e.g., 'UTC', 'America/New_York', 'Europe/Amsterdam'). Defaults to UTC when omitted." - ), - ] = None - by: Annotated[ - float | None, - Field( - description="For transform 'divide': the divisor to apply (e.g., 100 to convert integer cents to decimal dollars).", - gt=0.0, - ), - ] = None - separator: Annotated[ - str | None, - Field( - description="For transform 'split': the separator character or string to split on. Defaults to ','." - ), - ] = ',' - default: Annotated[ - Any | None, - Field( - description='Fallback value to use when feed_field is absent, null, or empty. Applied after any transform would have been applied. Allows optional feed fields to have a guaranteed baseline value.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FormatOptionRefs1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['publisher'], - Field( - description="Reference resolves against the named publisher's adagents.json top-level `formats[]` catalog." - ), - ] = 'publisher' - publisher_domain: Annotated[ - str, - Field( - description='Publisher domain where the adagents.json declaring this format option is hosted.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the publisher's adagents.json top-level `formats[]`, matching a publisher-catalog-backed entry in the target product's `format_options[]`." - ), - ] - - -class FormatOptionRefs2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Reference resolves only against the target product's inline `format_options[]`." - ), - ] = 'product' - format_option_id: Annotated[ - str, - Field( - description="Stable format option ID from the target product's inline `format_options[]`." - ), - ] - publisher_domain: Any | None = None - - -class FormatOptionRefs(RootModel[FormatOptionRefs1 | FormatOptionRefs2]): - root: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoCountry(Country): - pass - - -class GeoCountriesExcludeItem(Country): - pass - - -class GeoRegion(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}-[A-Z0-9]{1,3}$')] - - -class GeoRegionsExcludeItem(GeoRegion): - pass - - -class System(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - zip_1 = 'zip' - zip_plus_four_1 = 'zip_plus_four' - - -class GeoPostalAreas11(AdCPBaseModel): - country: Annotated[ - Literal['US'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'US' - system: Annotated[ - System, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System1(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - outward_1 = 'outward' - full_1 = 'full' - - -class GeoPostalAreas12(AdCPBaseModel): - country: Annotated[ - Literal['GB'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'GB' - system: Annotated[ - System1, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class System2(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - fsa_1 = 'fsa' - full_1 = 'full' - - -class GeoPostalAreas13(AdCPBaseModel): - country: Annotated[ - Literal['CA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'CA' - system: Annotated[ - System2, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class Country52(StrEnum): - DE = 'DE' - CH = 'CH' - AT = 'AT' - - -class System3(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class GeoPostalAreas14(AdCPBaseModel): - country: Annotated[Country52, Field(description='ISO 3166-1 alpha-2 country code.')] - system: Annotated[ - Literal['plz'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'plz' - - -class GeoPostalAreas15(AdCPBaseModel): - country: Annotated[ - Literal['FR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'FR' - system: Annotated[ - Literal['code_postal'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'code_postal' - - -class GeoPostalAreas16(AdCPBaseModel): - country: Annotated[ - Literal['AU'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'AU' - system: Annotated[ - Literal['postcode'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postcode' - - -class GeoPostalAreas17(AdCPBaseModel): - country: Annotated[ - Literal['BR'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'BR' - system: Annotated[ - Literal['cep'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'cep' - - -class GeoPostalAreas18(AdCPBaseModel): - country: Annotated[ - Literal['IN'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'IN' - system: Annotated[ - Literal['pin'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'pin' - - -class GeoPostalAreas19(AdCPBaseModel): - country: Annotated[ - Literal['ZA'], Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] = 'ZA' - system: Annotated[ - Literal['postal_code'], - Field(description='Country-local postal code system.', title='Postal Code System'), - ] = 'postal_code' - - -class System9(StrEnum): - postal_code = 'postal_code' - zip = 'zip' - zip_plus_four = 'zip_plus_four' - outward = 'outward' - full = 'full' - fsa = 'fsa' - plz = 'plz' - code_postal = 'code_postal' - postcode = 'postcode' - cep = 'cep' - pin = 'pin' - custom = 'custom' - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - postal_code_1 = 'postal_code' - custom_1 = 'custom' - - -class GeoPostalAreas110(AdCPBaseModel): - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: Annotated[ - System9, Field(description='Country-local postal code system.', title='Postal Code System') - ] - - -class GeoPostalAreasExclude1(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude2(GeoPostalAreas12): - pass - - -class GeoPostalAreasExclude3(GeoPostalAreas13): - pass - - -class GeoPostalAreasExclude4(GeoPostalAreas14): - pass - - -class GeoPostalAreasExclude5(GeoPostalAreas15): - pass - - -class GeoPostalAreasExclude6(GeoPostalAreas16): - pass - - -class GeoPostalAreasExclude7(GeoPostalAreas17): - pass - - -class GeoPostalAreasExclude8(GeoPostalAreas18): - pass - - -class GeoPostalAreasExclude9(GeoPostalAreas19): - pass - - -class GeoPostalAreasExclude10(GeoPostalAreas110): - pass - - -class Operator(StrEnum): - any = 'any' - none = 'none' - - - - -class Signal31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - - -class Signal34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal35(Signal31, Signal34): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal36(Signal32, Signal34): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal37(Signal33, Signal34): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal(RootModel[Signal35 | Signal36 | Signal37]): - root: Annotated[ - Signal35 | Signal36 | Signal37, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - - - -class SignalTargeting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting1(RootModel[SignalTargeting | SignalTargeting3 | SignalTargeting4]): - root: Annotated[ - SignalTargeting | SignalTargeting3 | SignalTargeting4, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress(Window): - pass - - -class Window5(Window): - pass - - -class PropertyList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the property list')] - list_id: Annotated[ - str, Field(description='Identifier for the property list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionList(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[AnyUrl, Field(description='URL of the agent managing the collection list')] - list_id: Annotated[ - str, Field(description='Identifier for the collection list within the agent', min_length=1) - ] - auth_token: Annotated[ - str | None, - Field( - description='JWT or other authorization token for accessing the list. Optional if the list is public or caller has implicit access.' - ), - ] = None - - -class CollectionListExclude(CollectionList): - pass - - -class StoreCatchment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str, Field(description='Synced store-type catalog ID from sync_catalogs.') - ] - store_ids: Annotated[ - list[str] | None, - Field( - description='Filter to specific stores within the catalog. Omit to target all stores.', - min_length=1, - ), - ] = None - catchment_ids: Annotated[ - list[str] | None, - Field( - description="Catchment zone IDs to target (e.g., 'walk', 'drive'). Omit to target all catchment zones.", - min_length=1, - ), - ] = None - - -class Type28(StrEnum): - Polygon = 'Polygon' - MultiPolygon = 'MultiPolygon' - - -class Geometry(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Annotated[Type28, Field(description='GeoJSON geometry type.')] - coordinates: Annotated[ - list[Any], - Field( - description='GeoJSON coordinates array. For Polygon: array of linear rings. For MultiPolygon: array of polygons.' - ), - ] - - -class LanguageItem(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z]{2}$')] - - -class DeclaredBy266(DeclaredBy): - pass - - -class VerifyAgent532(VerifyAgent): - pass - - -class VerifyAgent533(VerifyAgent439): - pass - - -class VerificationItem266(VerificationItem): - pass - - -class DeclaredBy267(DeclaredBy): - pass - - -class VerifyAgent534(VerifyAgent): - pass - - -class VerifyAgent535(VerifyAgent439): - pass - - -class VerificationItem267(VerificationItem): - pass - - -class AttributionWindow(NoticePeriod): - pass - - -class DeclaredBy268(DeclaredBy): - pass - - -class VerifyAgent536(VerifyAgent): - pass - - -class VerifyAgent537(VerifyAgent439): - pass - - -class VerificationItem268(VerificationItem): - pass - - -class CreativeAssignment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Unique identifier for the creative')] - weight: Annotated[ - float | None, - Field( - description='Relative delivery weight for this creative (0–100). When multiple creatives are assigned to the same package, weights determine impression distribution proportionally — a creative with weight 2 gets twice the delivery of weight 1. When omitted, the creative receives equal rotation with other unweighted creatives. A weight of 0 means the creative is assigned but paused (receives no delivery).', - ge=0.0, - le=100.0, - ), - ] = None - placement_refs: Annotated[ - list[PlacementRef] | None, - Field( - description="Optional array of structured placement references where this creative should run within the already-purchased package inventory. New senders SHOULD use this field for placement-level creative routing because placement IDs are publisher-scoped. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. References entries from the product's `placements[]` array by `{ publisher_domain, placement_id }`; if `publisher_domain` is omitted in the ref, receivers MAY interpret it relative to the seller agent's own publisher domain in legacy single-publisher contexts. If both `placement_refs` and legacy `placement_ids` are present, `placement_refs` wins and receivers MUST ignore `placement_ids`.", - min_length=1, - ), - ] = None - placement_ids: Annotated[ - list[str] | None, - Field( - description="Legacy shorthand array of placement IDs where this creative should run within the already-purchased package inventory. New senders SHOULD use `placement_refs` because placement IDs are publisher-scoped and strings are ambiguous in multi-publisher products. This field does not narrow the purchased package inventory by itself; use product refinement or seller-supported package targeting to buy only one placement. When omitted, the creative runs on all buyer-targetable placements in the package. Receivers MAY interpret string IDs relative to the seller agent's own publisher domain in legacy single-publisher contexts. If `placement_refs` is also present, receivers MUST ignore this field.", - min_length=1, - ), - ] = None - - -class FormatIdsToProvideItem(FormatId): - pass - - -class Window6(Window): - pass - - -class TargetFrequency(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window6, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - -class Target(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, Field(description='Target cost per metric unit in the buy currency', gt=0.0) - ] - - -class Target18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units depend on the metric: proportion (clicks, views, completed_views), seconds (viewed_seconds, attention_seconds), or score (attention_score).', - gt=0.0, - ), - ] - - -class Target19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[float, Field(description='Target cost per event in the buy currency', gt=0.0)] - - -class Target20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['per_ad_spend'] = 'per_ad_spend' - value: Annotated[ - float, - Field(description='Target return ratio (e.g., 4.0 means $4 of value per $1 spent)', gt=0.0), - ] - - -class Target21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['maximize_value'] = 'maximize_value' - - -class PostClick(Window): - pass - - -class PostView(Window): - pass - - -class DeclaredBy269(DeclaredBy): - pass - - -class VerifyAgent538(VerifyAgent): - pass - - -class VerifyAgent539(VerifyAgent439): - pass - - -class VerificationItem269(VerificationItem): - pass - - -class Target22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['cost_per'] = 'cost_per' - value: Annotated[ - float, - Field( - description='Target cost per metric unit in the buy currency. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Target23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['threshold_rate'] = 'threshold_rate' - value: Annotated[ - float, - Field( - description='Minimum per-impression value. Units of the metric are vendor-defined.', - gt=0.0, - ), - ] - - -class Geo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - countries: Annotated[ - list[str] | None, - Field(description='ISO 3166-1 alpha-2 country codes where ads will deliver.'), - ] = None - regions: Annotated[ - list[str] | None, Field(description='ISO 3166-2 subdivision codes where ads will deliver.') - ] = None - - -class Suppress1(Window): - pass - - -class Window7(Window): - pass - - - - - - -class AudienceTargeting(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class AudienceTargeting3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class AudienceTargeting4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['signal'], Field(description='Discriminator for signal-based selectors') - ] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New selectors SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description='Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided.' - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description='Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided.' - ), - ] = None - - -class AudienceTargeting5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['description'], Field(description='Discriminator for description-based selectors') - ] = 'description' - description: Annotated[ - str, - Field( - description="Natural language description of the audience (e.g., 'likely EV buyers', 'high net worth individuals', 'vulnerable communities')", - max_length=2000, - min_length=1, - ), - ] - category: Annotated[ - str | None, - Field( - description="Optional grouping hint for the governance agent (e.g., 'demographic', 'behavioral', 'contextual', 'financial')" - ), - ] = None - - -class Issue28(Issue): - pass - - -class AdcpError16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue28] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue29(Issue): - pass - - -class Error10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue29] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue30(Issue): - pass - - -class AdcpError17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue30] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue31(Issue): - pass - - -class Error11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue31] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason14(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - BUDGET_EXCEEDS_LIMIT = 'BUDGET_EXCEEDS_LIMIT' - - -class Issue32(Issue): - pass - - -class Error12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue32] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason14 | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[Error12] | None, - Field( - description='Optional validation errors or warnings for debugging purposes. Helps explain why input is required.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue33(Issue): - pass - - -class Error13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue33] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose status field carries a MediaBuyStatus value (pending_creatives, pending_start, active). See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The media_buy_id is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting IO signature from sales team; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error13] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue34(Issue): - pass - - -class AdcpError18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue34] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Contact11(Contact): - pass - - -class InvoiceRecipient1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact11] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FeedFieldMapping9(FeedFieldMapping): - pass - - - -class FormatOptionRefs3(RootModel[FormatOptionRefs1 | FormatOptionRefs2]): - root: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2, - Field( - description='Discriminated reference to a product format option. The global canonical shape is still named by `format_kind`; this reference selects one concrete product `format_options[]` entry. `scope: "publisher"` identifies a publisher-declared catalog option by `{ publisher_domain, format_option_id }`. `scope: "product"` identifies a product-local option by `format_option_id`; the enclosing package/product context supplies the namespace.', - discriminator='scope', - title='Format Option Reference', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas31(GeoPostalAreas11): - pass - - -class GeoPostalAreas32(GeoPostalAreas12): - pass - - -class GeoPostalAreas33(GeoPostalAreas13): - pass - - -class GeoPostalAreas34(GeoPostalAreas14): - pass - - -class GeoPostalAreas35(GeoPostalAreas15): - pass - - -class GeoPostalAreas36(GeoPostalAreas16): - pass - - -class GeoPostalAreas37(GeoPostalAreas17): - pass - - -class GeoPostalAreas38(GeoPostalAreas18): - pass - - -class GeoPostalAreas39(GeoPostalAreas19): - pass - - -class GeoPostalAreas310(GeoPostalAreas110): - pass - - -class GeoPostalAreasExclude231(GeoPostalAreas11): - pass - - -class GeoPostalAreasExclude232(GeoPostalAreas12): - pass - - -class GeoPostalAreasExclude233(GeoPostalAreas13): - pass - - -class GeoPostalAreasExclude234(GeoPostalAreas14): - pass - - -class GeoPostalAreasExclude235(GeoPostalAreas15): - pass - - -class GeoPostalAreasExclude236(GeoPostalAreas16): - pass - - -class GeoPostalAreasExclude237(GeoPostalAreas17): - pass - - -class GeoPostalAreasExclude238(GeoPostalAreas18): - pass - - -class GeoPostalAreasExclude239(GeoPostalAreas19): - pass - - -class GeoPostalAreasExclude2310(GeoPostalAreas110): - pass - - - - -class Signal41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals.')] = 'binary' - value: Annotated[ - Literal[True], - Field( - description='Binary package signal entries match users for whom the signal is true. Use the parent group operator for include/exclude.' - ), - ] - - - - -class Signal42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals.') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values match the expression.', - min_length=1, - ), - ] - - - - -class Signal43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description='Named signal being targeted.', discriminator='scope', title='Signal Ref' - ), - ] - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals.') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value, inclusive. Omit for no minimum. Should be within the signal definition's range when declared." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value, inclusive. Omit for no maximum. Should be within the signal definition's range when declared." - ), - ] = None - - - -class Signal44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal45(Signal41, Signal44): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal46(Signal42, Signal44): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - - -class Signal47(Signal43, Signal44): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str | None, - Field( - description="Pricing option selected for this signal. Use the pricing_option_id from the product's signal_targeting_options entry when product-scoped pricing is present; otherwise use the seller get_signals pricing only when the product option does not override it. Required when the selected signal has pricing_options; omit only when the signal is bundled into the product price or has no incremental cost." - ), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the product option exposes a separate runtime or activation handle, and pass it verbatim. Buyers SHOULD prefer an exposed segment handle over reconstructing condition identity from categorical values because the handle can carry provider namespace and methodology distinctions.' - ), - ] = None - activation_key: Annotated[ - ActivationKey | ActivationKey8 | None, - Field( - description='Destination-specific activation key returned by get_signals or activate_signal. Usually omitted for seller-offered signals selected directly through the same seller; include only when the selected signal was separately activated and the seller requires the activation key to correlate the package selection.', - discriminator='type', - title='Activation Key', - ), - ] = None - - -class Signal4(RootModel[Signal45 | Signal46 | Signal47]): - root: Annotated[ - Signal45 | Signal46 | Signal47, - Field( - description="Buy-time selection of one seller-offered signal inside a package signal targeting group. The signal_ref uses scope 'product' for a product-local signal option, scope 'data_provider' for a signal defined in a data provider's published adagents.json signals[], or scope 'signal_source' for a source-native signal that is not published in adagents.json signals[]. The selected product's inline Product.signal_targeting_options, get_signals feed when inline options are omitted, and signal_targeting_rules define buy-time eligibility. Inclusion and exclusion are controlled by the parent group operator: use operator 'any' to include users matching the signal expression and operator 'none' to exclude users matching the signal expression. For binary signals, value MUST be true; do not use value=false for exclusion inside signal_targeting_groups. Use audience_include/audience_exclude only for buyer-managed first-party audiences registered through sync_audiences.", - title='Package Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Group1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Operator, - Field( - description="How to evaluate the signals in this group. 'any' is an OR include group. 'none' is an exclusion group equivalent to NOT (A OR B OR C)." - ), - ] - signals: Annotated[ - list[Signal4], - Field( - description='Signal targeting entries evaluated by this group. Each entry uses the package signal targeting shape, including signal_ref, value expression, and optional pricing, execution-handle, or activation fields.', - min_length=1, - ), - ] - - -class SignalTargetingGroups1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - operator: Annotated[ - Literal['all'], - Field( - description="Groups-level operator. Required even though v1 only supports 'all': every child group must be satisfied." - ), - ] = 'all' - groups: Annotated[ - list[Group1], - Field( - description="Signal targeting groups to evaluate. Use operator 'any' for include groups and 'none' for exclusion groups.", - min_length=1, - ), - ] - - - - - - -class SignalTargeting6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalTargeting7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalTargeting8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - -class SignalTargeting5(RootModel[SignalTargeting6 | SignalTargeting7 | SignalTargeting8]): - root: Annotated[ - SignalTargeting6 | SignalTargeting7 | SignalTargeting8, - Field( - description='Targeting constraint for a specific signal. Uses value_type as discriminator to determine the targeting expression format.', - discriminator='value_type', - title='Signal Targeting', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Suppress2(Window): - pass - - -class Window8(Window): - pass - - -class Geometry2(Geometry): - pass - - -class DeclaredBy270(DeclaredBy): - pass - - -class VerifyAgent540(VerifyAgent): - pass - - -class VerifyAgent541(VerifyAgent439): - pass - - -class VerificationItem270(VerificationItem): - pass - - -class DeclaredBy271(DeclaredBy): - pass - - -class VerifyAgent542(VerifyAgent): - pass - - -class VerifyAgent543(VerifyAgent439): - pass - - -class VerificationItem271(VerificationItem): - pass - - -class AttributionWindow6(NoticePeriod): - pass - - -class DeclaredBy272(DeclaredBy): - pass - - -class VerifyAgent544(VerifyAgent): - pass - - -class VerifyAgent545(VerifyAgent439): - pass - - -class VerificationItem272(VerificationItem): - pass - - -class CreativeAssignment2(CreativeAssignment): - pass - - -class Window9(Window): - pass - - -class TargetFrequency1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - min: Annotated[ - int | None, - Field( - description='Minimum frequency for an entity to be considered meaningfully reached within the window. Impressions that would bring an entity below this threshold are treated as high-value (growing reach). When omitted, the seller uses their platform default (typically 1).', - ge=1, - ), - ] = None - max: Annotated[ - int | None, - Field( - description='Frequency at which an entity is considered saturated within the window. Impressions toward entities at or above this threshold are treated as lower-value. When both min and max are present, max must be greater than or equal to min. When omitted, the seller determines the saturation point.', - ge=1, - ), - ] = None - window: Annotated[ - Window9, - Field( - description='Time window over which frequency is measured (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Weekly windows are typical for brand campaigns; daily windows suit high-cadence direct response.' - ), - ] - - - - - - -class PostClick1(Window): - pass - - -class PostView1(Window): - pass - - -class DeclaredBy273(DeclaredBy): - pass - - -class VerifyAgent546(VerifyAgent): - pass - - -class VerifyAgent547(VerifyAgent439): - pass - - -class VerificationItem273(VerificationItem): - pass - - - -class Issue35(Issue): - pass - - -class AdcpError19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue35] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue36(Issue): - pass - - -class Error14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue36] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue37(Issue): - pass - - -class AdcpError20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue37] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue38(Issue): - pass - - -class Error15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue38] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result290(Result280): - pass - - -class Reason15(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CHANGE_CONFIRMATION = 'CHANGE_CONFIRMATION' - - -class Result291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason15 | None, Field(description='Reason code indicating why input is needed') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue39(Issue): - pass - - -class Error16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue39] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose media_buy_id is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Awaiting operator re-approval; typical turnaround 2–4 hours.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error16] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class NotificationType1(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - window_update = 'window_update' - - -class ReportingPeriod(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: AwareDatetime - end: AwareDatetime - - -class PostClick2(Window): - pass - - -class PostView2(Window): - pass - - -class Status141(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - pending = 'pending' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - failed = 'failed' - reporting_delayed = 'reporting_delayed' - - -class Kind13(StrEnum): - cumulative = 'cumulative' - period = 'period' - rolling = 'rolling' - - -class Period10(Window): - pass - - -class ReachWindow(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind13, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period10 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class QuartileData(AdCPBaseModel): - q1_views: Annotated[float | None, Field(description='25% completion views', ge=0.0)] = None - q2_views: Annotated[float | None, Field(description='50% completion views', ge=0.0)] = None - q3_views: Annotated[float | None, Field(description='75% completion views', ge=0.0)] = None - q4_views: Annotated[float | None, Field(description='100% completion views', ge=0.0)] = None - - -class VenueBreakdownItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - venue_id: Annotated[str, Field(description='Venue identifier')] - venue_name: Annotated[str | None, Field(description='Human-readable venue name')] = None - venue_type: Annotated[ - str | None, - Field(description="Venue type (e.g., 'airport', 'transit', 'retail', 'billboard')"), - ] = None - impressions: Annotated[int, Field(description='Impressions delivered at this venue', ge=0)] - loop_plays: Annotated[int | None, Field(description='Loop plays at this venue', ge=0)] = None - screens_used: Annotated[ - int | None, Field(description='Number of screens used at this venue', ge=0) - ] = None - - -class DoohMetrics(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - loop_plays: Annotated[ - int | None, Field(description='Number of times ad played in rotation', ge=0) - ] = None - screens_used: Annotated[ - int | None, Field(description='Number of unique screens displaying the ad', ge=0) - ] = None - screen_time_seconds: Annotated[ - int | None, Field(description='Total display time in seconds', ge=0) - ] = None - sov_achieved: Annotated[ - float | None, - Field(description='Actual share of voice delivered (0.0 to 1.0)', ge=0.0, le=1.0), - ] = None - calculation_notes: Annotated[ - str | None, - Field( - description="Per-row supplementary methodology notes for DOOH impression calculation (e.g., 'rotation-based; 6-second slot weighted by 70% audience overlap'). Free-form prose for context that doesn't fit the structured measurement-vendor surface. Canonical methodology declarations belong on the measurement vendor's `get_adcp_capabilities.measurement.metrics[]` block where they're discoverable once and inherited across delivery rows; this field is for row-specific context (a particular daypart's calculation, a venue-mix exception) rather than the seller's general methodology." - ), - ] = None - venue_breakdown: Annotated[ - list[VenueBreakdownItem] | None, Field(description='Per-venue performance breakdown') - ] = None - - -class DeclaredBy274(DeclaredBy): - pass - - -class VerifyAgent548(VerifyAgent): - pass - - -class VerifyAgent549(VerifyAgent439): - pass - - -class VerificationItem274(VerificationItem): - pass - - -class DeclaredBy275(DeclaredBy): - pass - - -class VerifyAgent550(VerifyAgent): - pass - - -class VerifyAgent551(VerifyAgent439): - pass - - -class VerificationItem275(VerificationItem): - pass - - -class Period11(Window): - pass - - -class ReachWindow5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind13, - Field( - description="Window semantics. `cumulative` — uniques since campaign start; the value is the total unique count to date and MUST NOT be summed across rows (each later row supersedes the earlier value). `period` — uniques within a single non-overlapping reporting period (e.g., a daily snapshot for a specific calendar day). Adjacent `period` rows do not share audiences by construction, but the same person MAY appear across multiple periods, so MUST NOT be summed across rows to compute campaign reach. `rolling` — uniques within a trailing window ending at the row's reporting timestamp (e.g., trailing-7-day reach). Adjacent rolling rows overlap and MUST NOT be summed; each row's value stands alone." - ), - ] - period: Annotated[ - Period11 | None, - Field( - description='Duration of the measurement window. REQUIRED when `kind` is `period` or `rolling` — declares the snapshot length (e.g., `{"interval": 1, "unit": "days"}` for a daily snapshot) or the trailing-window length (e.g., `{"interval": 7, "unit": "days"}` for trailing-7-day rolling reach). When `kind` is `cumulative`, this field is implicit (campaign-to-date) and SHOULD be omitted.' - ), - ] = None - - -class DoohMetrics4(DoohMetrics): - pass - - -class DeclaredBy276(DeclaredBy): - pass - - -class VerifyAgent552(VerifyAgent): - pass - - -class VerifyAgent553(VerifyAgent439): - pass - - -class VerificationItem276(VerificationItem): - pass - - -class DeclaredBy277(DeclaredBy): - pass - - -class VerifyAgent554(VerifyAgent): - pass - - -class VerifyAgent555(VerifyAgent439): - pass - - -class VerificationItem277(VerificationItem): - pass - - -class DeliveryStatus(StrEnum): - delivering = 'delivering' - completed = 'completed' - budget_exhausted = 'budget_exhausted' - flight_ended = 'flight_ended' - goal_met = 'goal_met' - - -class Issue40(Issue): - pass - - -class Error17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue40] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue41(Issue): - pass - - -class AdcpError21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue41] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - -class DeclaredBy278(DeclaredBy): - pass - - -class VerifyAgent556(VerifyAgent): - pass - - -class VerifyAgent557(VerifyAgent439): - pass - - -class VerificationItem278(VerificationItem): - pass - - -class ColorSpace(StrEnum): - rec709 = 'rec709' - rec2020 = 'rec2020' - rec2100 = 'rec2100' - srgb = 'srgb' - dci_p3 = 'dci_p3' - - -class HdrFormat(StrEnum): - sdr = 'sdr' - hdr10 = 'hdr10' - hdr10_plus = 'hdr10_plus' - hlg = 'hlg' - dolby_vision = 'dolby_vision' - - -class ChromaSubsampling(StrEnum): - field_4_2_0 = '4:2:0' - field_4_2_2 = '4:2:2' - field_4_4_4 = '4:4:4' - - -class VideoBitDepth(IntEnum): - integer_8 = 8 - integer_10 = 10 - integer_12 = 12 - - -class AudioBitDepth(IntEnum): - integer_16 = 16 - integer_24 = 24 - integer_32 = 32 - - -class DeclaredBy279(DeclaredBy): - pass - - -class VerifyAgent558(VerifyAgent): - pass - - -class VerifyAgent559(VerifyAgent439): - pass - - -class VerificationItem279(VerificationItem): - pass - - -class DeclaredBy280(DeclaredBy): - pass - - -class VerifyAgent560(VerifyAgent): - pass - - -class VerifyAgent561(VerifyAgent439): - pass - - -class VerificationItem280(VerificationItem): - pass - - -class DeclaredBy281(DeclaredBy): - pass - - -class VerifyAgent562(VerifyAgent): - pass - - -class VerifyAgent563(VerifyAgent439): - pass - - -class VerificationItem281(VerificationItem): - pass - - -class DeclaredBy282(DeclaredBy): - pass - - -class VerifyAgent564(VerifyAgent): - pass - - -class VerifyAgent565(VerifyAgent439): - pass - - -class VerificationItem282(VerificationItem): - pass - - -class DeclaredBy283(DeclaredBy): - pass - - -class VerifyAgent566(VerifyAgent): - pass - - -class VerifyAgent567(VerifyAgent439): - pass - - -class VerificationItem283(VerificationItem): - pass - - -class DeclaredBy284(DeclaredBy): - pass - - -class VerifyAgent568(VerifyAgent): - pass - - -class VerifyAgent569(VerifyAgent439): - pass - - -class VerificationItem284(VerificationItem): - pass - - -class Accessibility(AdCPBaseModel): - alt_text: Annotated[ - str | None, Field(description='Text alternative describing the creative content') - ] = None - keyboard_navigable: Annotated[ - bool | None, Field(description='Whether the creative can be fully operated via keyboard') - ] = None - motion_control: Annotated[ - bool | None, - Field( - description='Whether the creative respects prefers-reduced-motion or provides pause/stop controls' - ), - ] = None - screen_reader_tested: Annotated[ - bool | None, Field(description='Whether the creative has been tested with screen readers') - ] = None - - -class DeclaredBy285(DeclaredBy): - pass - - -class VerifyAgent570(VerifyAgent): - pass - - -class VerifyAgent571(VerifyAgent439): - pass - - -class VerificationItem285(VerificationItem): - pass - - -class DeclaredBy286(DeclaredBy): - pass - - -class VerifyAgent572(VerifyAgent): - pass - - -class VerifyAgent573(VerifyAgent439): - pass - - -class VerificationItem286(VerificationItem): - pass - - -class DeclaredBy287(DeclaredBy): - pass - - -class VerifyAgent574(VerifyAgent): - pass - - -class VerifyAgent575(VerifyAgent439): - pass - - -class VerificationItem287(VerificationItem): - pass - - -class DeclaredBy288(DeclaredBy): - pass - - -class VerifyAgent576(VerifyAgent): - pass - - -class VerifyAgent577(VerifyAgent439): - pass - - -class VerificationItem288(VerificationItem): - pass - - -class DeclaredBy289(DeclaredBy): - pass - - -class VerifyAgent578(VerifyAgent): - pass - - -class VerifyAgent579(VerifyAgent439): - pass - - -class VerificationItem289(VerificationItem): - pass - - -class DeclaredBy290(DeclaredBy): - pass - - -class VerifyAgent580(VerifyAgent): - pass - - -class VerifyAgent581(VerifyAgent439): - pass - - -class VerificationItem290(VerificationItem): - pass - - -class DeclaredBy291(DeclaredBy): - pass - - -class VerifyAgent582(VerifyAgent): - pass - - -class VerifyAgent583(VerifyAgent439): - pass - - -class VerificationItem291(VerificationItem): - pass - - -class Objective(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - conversion = 'conversion' - retention = 'retention' - engagement = 'engagement' - - -class Messaging(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - headline: Annotated[str | None, Field(description='Primary headline')] = None - tagline: Annotated[str | None, Field(description='Supporting tagline or sub-headline')] = None - cta: Annotated[str | None, Field(description='Call-to-action text')] = None - key_messages: Annotated[ - list[str] | None, Field(description='Key messages to communicate in priority order') - ] = None - - -class Role308(StrEnum): - style_reference = 'style_reference' - product_shot = 'product_shot' - mood_board = 'mood_board' - example_creative = 'example_creative' - logo = 'logo' - strategy_doc = 'strategy_doc' - storyboard = 'storyboard' - - -class ReferenceAsset(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - url: Annotated[ - AnyUrl, Field(description='URL to the reference asset (image, video, or document)') - ] - role: Annotated[ - Role308, - Field( - description='How the creative agent should use this asset. style_reference: match the visual style; product_shot: include this product; mood_board: overall look and feel; example_creative: example of a similar execution; logo: logo to use; strategy_doc: strategy or planning document for context; storyboard: sequential visual direction for video or multi-scene creative' - ), - ] - description: Annotated[ - str | None, - Field( - description='Human-readable description of the asset and how it should inform creative generation' - ), - ] = None - - -class Jurisdiction303(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}(-[A-Z0-9]{1,3})?$')] - - -class FeedFieldMapping10(FeedFieldMapping): - pass - - -class IdentityRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - handle: Annotated[ - str | None, Field(description='Human-readable handle or username when available.') - ] = None - profile_url: Annotated[ - AnyUrl | None, Field(description='URL for the authoring profile, page, or channel.') - ] = None - platform_identity_id: Annotated[ - str | None, Field(description='Opaque platform-native identity identifier.') - ] = None - - -class Status142(StrEnum): - authorized = 'authorized' - pending = 'pending' - expired = 'expired' - revoked = 'revoked' - not_required = 'not_required' - unknown = 'unknown' - - -class ReferenceAuthorization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status142, - Field( - description='Current seller-observed authorization state. `authorized`: seller can serve the referenced post. `pending`: additional creator/account action is required. `expired` or `revoked`: authorization previously existed but no longer permits serving. `not_required`: the seller can legally and technically serve without a separate identity authorization. `unknown`: seller has not checked or cannot disclose the state.' - ), - ] - checked_at: Annotated[ - AwareDatetime | None, - Field(description='When the seller last checked the authorization state.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When the authorization expires, if the platform exposes an expiry.'), - ] = None - authorization_url: Annotated[ - AnyUrl | None, - Field(description='Optional URL the buyer or creator can use to complete authorization.'), - ] = None - authorization_instructions: Annotated[ - str | None, - Field(description='Human-readable instructions for completing or restoring authorization.'), - ] = None - - -class DeclaredBy292(DeclaredBy): - pass - - -class VerifyAgent584(VerifyAgent): - pass - - -class VerifyAgent585(VerifyAgent439): - pass - - -class VerificationItem292(VerificationItem): - pass - - -class DeclaredBy293(DeclaredBy): - pass - - -class VerifyAgent586(VerifyAgent): - pass - - -class VerifyAgent587(VerifyAgent439): - pass - - -class VerificationItem293(VerificationItem): - pass - - -class DeclaredBy294(DeclaredBy): - pass - - -class VerifyAgent588(VerifyAgent): - pass - - -class VerifyAgent589(VerifyAgent439): - pass - - -class VerificationItem294(VerificationItem): - pass - - -class DeclaredBy295(DeclaredBy): - pass - - -class VerifyAgent590(VerifyAgent): - pass - - -class VerifyAgent591(VerifyAgent439): - pass - - -class VerificationItem295(VerificationItem): - pass - - -class DeclaredBy296(DeclaredBy): - pass - - -class VerifyAgent592(VerifyAgent): - pass - - -class VerifyAgent593(VerifyAgent439): - pass - - -class VerificationItem296(VerificationItem): - pass - - -class Event(StrEnum): - impression = 'impression' - viewable_mrc_50 = 'viewable_mrc_50' - viewable_mrc_100 = 'viewable_mrc_100' - viewable_video_50 = 'viewable_video_50' - audible_video_complete = 'audible_video_complete' - click = 'click' - custom = 'custom' - - -class Method19(StrEnum): - img = 'img' - js = 'js' - - -class DeclaredBy297(DeclaredBy): - pass - - -class VerifyAgent594(VerifyAgent): - pass - - -class VerifyAgent595(VerifyAgent439): - pass - - -class VerificationItem297(VerificationItem): - pass - - -class Target31(StrEnum): - linear = 'linear' - non_linear = 'non_linear' - companion = 'companion' - - -class DeclaredBy298(DeclaredBy): - pass - - -class VerifyAgent596(VerifyAgent): - pass - - -class VerifyAgent597(VerifyAgent439): - pass - - -class VerificationItem298(VerificationItem): - pass - - -class Target32(StrEnum): - linear = 'linear' - companion = 'companion' - - -class DeclaredBy299(DeclaredBy): - pass - - -class VerifyAgent598(VerifyAgent): - pass - - -class VerifyAgent599(VerifyAgent439): - pass - - -class VerificationItem299(VerificationItem): - pass - - -class DeclaredBy300(DeclaredBy): - pass - - -class VerifyAgent600(VerifyAgent): - pass - - -class VerifyAgent601(VerifyAgent439): - pass - - -class VerificationItem300(VerificationItem): - pass - - -class DeclaredBy301(DeclaredBy): - pass - - -class VerifyAgent602(VerifyAgent): - pass - - -class VerifyAgent603(VerifyAgent439): - pass - - -class VerificationItem301(VerificationItem): - pass - - -class DeclaredBy302(DeclaredBy): - pass - - -class VerifyAgent604(VerifyAgent): - pass - - -class VerifyAgent605(VerifyAgent439): - pass - - -class VerificationItem302(VerificationItem): - pass - - -class DeclaredBy303(DeclaredBy): - pass - - -class VerifyAgent606(VerifyAgent): - pass - - -class VerifyAgent607(VerifyAgent439): - pass - - -class VerificationItem303(VerificationItem): - pass - - -class DeclaredBy304(DeclaredBy): - pass - - -class VerifyAgent608(VerifyAgent): - pass - - -class VerifyAgent609(VerifyAgent439): - pass - - -class VerificationItem304(VerificationItem): - pass - - -class DeclaredBy305(DeclaredBy): - pass - - -class VerifyAgent610(VerifyAgent): - pass - - -class VerifyAgent611(VerifyAgent439): - pass - - -class VerificationItem305(VerificationItem): - pass - - -class DeclaredBy306(DeclaredBy): - pass - - -class VerifyAgent612(VerifyAgent): - pass - - -class VerifyAgent613(VerifyAgent439): - pass - - -class VerificationItem306(VerificationItem): - pass - - -class DeclaredBy307(DeclaredBy): - pass - - -class VerifyAgent614(VerifyAgent): - pass - - -class VerifyAgent615(VerifyAgent439): - pass - - -class VerificationItem307(VerificationItem): - pass - - -class DeclaredBy308(DeclaredBy): - pass - - -class VerifyAgent616(VerifyAgent): - pass - - -class VerifyAgent617(VerifyAgent439): - pass - - -class VerificationItem308(VerificationItem): - pass - - -class DeclaredBy309(DeclaredBy): - pass - - -class VerifyAgent618(VerifyAgent): - pass - - -class VerifyAgent619(VerifyAgent439): - pass - - -class VerificationItem309(VerificationItem): - pass - - -class DeclaredBy310(DeclaredBy): - pass - - -class VerifyAgent620(VerifyAgent): - pass - - -class VerifyAgent621(VerifyAgent439): - pass - - -class VerificationItem310(VerificationItem): - pass - - -class DeclaredBy311(DeclaredBy): - pass - - -class VerifyAgent622(VerifyAgent): - pass - - -class VerifyAgent623(VerifyAgent439): - pass - - -class VerificationItem311(VerificationItem): - pass - - -class DeclaredBy312(DeclaredBy): - pass - - -class VerifyAgent624(VerifyAgent): - pass - - -class VerifyAgent625(VerifyAgent439): - pass - - -class VerificationItem312(VerificationItem): - pass - - -class DeclaredBy313(DeclaredBy): - pass - - -class VerifyAgent626(VerifyAgent): - pass - - -class VerifyAgent627(VerifyAgent439): - pass - - -class VerificationItem313(VerificationItem): - pass - - -class ReferenceAsset10(ReferenceAsset): - pass - - -class FeedFieldMapping11(FeedFieldMapping): - pass - - -class ReferenceAuthorization9(ReferenceAuthorization): - pass - - -class DeclaredBy314(DeclaredBy): - pass - - -class VerifyAgent628(VerifyAgent): - pass - - -class VerifyAgent629(VerifyAgent439): - pass - - -class VerificationItem314(VerificationItem): - pass - - -class DeclaredBy315(DeclaredBy): - pass - - -class VerifyAgent630(VerifyAgent): - pass - - -class VerifyAgent631(VerifyAgent439): - pass - - -class VerificationItem315(VerificationItem): - pass - - -class DeclaredBy316(DeclaredBy): - pass - - -class VerifyAgent632(VerifyAgent): - pass - - -class VerifyAgent633(VerifyAgent439): - pass - - -class VerificationItem316(VerificationItem): - pass - - -class DeclaredBy317(DeclaredBy): - pass - - -class VerifyAgent634(VerifyAgent): - pass - - -class VerifyAgent635(VerifyAgent439): - pass - - -class VerificationItem317(VerificationItem): - pass - - -class DeclaredBy318(DeclaredBy): - pass - - -class VerifyAgent636(VerifyAgent): - pass - - -class VerifyAgent637(VerifyAgent439): - pass - - -class VerificationItem318(VerificationItem): - pass - - -class DeclaredBy319(DeclaredBy): - pass - - -class VerifyAgent638(VerifyAgent): - pass - - -class VerifyAgent639(VerifyAgent439): - pass - - -class VerificationItem319(VerificationItem): - pass - - -class DeclaredBy320(DeclaredBy): - pass - - -class VerifyAgent640(VerifyAgent): - pass - - -class VerifyAgent641(VerifyAgent439): - pass - - -class VerificationItem320(VerificationItem): - pass - - -class DeclaredBy321(DeclaredBy): - pass - - -class VerifyAgent642(VerifyAgent): - pass - - -class VerifyAgent643(VerifyAgent439): - pass - - -class VerificationItem321(VerificationItem): - pass - - -class DeclaredBy322(DeclaredBy): - pass - - -class VerifyAgent644(VerifyAgent): - pass - - -class VerifyAgent645(VerifyAgent439): - pass - - -class VerificationItem322(VerificationItem): - pass - - -class RightsAgent(AdCPBaseModel): - url: Annotated[AnyUrl, Field(description='MCP endpoint URL of the rights agent')] - id: Annotated[str, Field(description='Agent identifier')] - - -class Country56(Country): - pass - - -class ExcludedCountry(Country): - pass - - -class ApprovalStatus(StrEnum): - pending = 'pending' - approved = 'approved' - rejected = 'rejected' - - -class DeclaredBy323(DeclaredBy): - pass - - -class VerifyAgent646(VerifyAgent): - pass - - -class VerifyAgent647(VerifyAgent439): - pass - - -class VerificationItem323(VerificationItem): - pass - - - -class DeclaredBy324(DeclaredBy): - pass - - -class VerifyAgent648(VerifyAgent): - pass - - -class VerifyAgent649(VerifyAgent439): - pass - - -class VerificationItem324(VerificationItem): - pass - - -class DeclaredBy325(DeclaredBy): - pass - - -class VerifyAgent650(VerifyAgent): - pass - - -class VerifyAgent651(VerifyAgent439): - pass - - -class VerificationItem325(VerificationItem): - pass - - -class DeclaredBy326(DeclaredBy): - pass - - -class VerifyAgent652(VerifyAgent): - pass - - -class VerifyAgent653(VerifyAgent439): - pass - - -class VerificationItem326(VerificationItem): - pass - - -class DeclaredBy327(DeclaredBy): - pass - - -class VerifyAgent654(VerifyAgent): - pass - - -class VerifyAgent655(VerifyAgent439): - pass - - -class VerificationItem327(VerificationItem): - pass - - -class DeclaredBy328(DeclaredBy): - pass - - -class VerifyAgent656(VerifyAgent): - pass - - -class VerifyAgent657(VerifyAgent439): - pass - - -class VerificationItem328(VerificationItem): - pass - - -class DeclaredBy329(DeclaredBy): - pass - - -class VerifyAgent658(VerifyAgent): - pass - - -class VerifyAgent659(VerifyAgent439): - pass - - -class VerificationItem329(VerificationItem): - pass - - -class DeclaredBy330(DeclaredBy): - pass - - -class VerifyAgent660(VerifyAgent): - pass - - -class VerifyAgent661(VerifyAgent439): - pass - - -class VerificationItem330(VerificationItem): - pass - - -class DeclaredBy331(DeclaredBy): - pass - - -class VerifyAgent662(VerifyAgent): - pass - - -class VerifyAgent663(VerifyAgent439): - pass - - -class VerificationItem331(VerificationItem): - pass - - -class DeclaredBy332(DeclaredBy): - pass - - -class VerifyAgent664(VerifyAgent): - pass - - -class VerifyAgent665(VerifyAgent439): - pass - - -class VerificationItem332(VerificationItem): - pass - - -class DeclaredBy333(DeclaredBy): - pass - - -class VerifyAgent666(VerifyAgent): - pass - - -class VerifyAgent667(VerifyAgent439): - pass - - -class VerificationItem333(VerificationItem): - pass - - -class DeclaredBy334(DeclaredBy): - pass - - -class VerifyAgent668(VerifyAgent): - pass - - -class VerifyAgent669(VerifyAgent439): - pass - - -class VerificationItem334(VerificationItem): - pass - - -class DeclaredBy335(DeclaredBy): - pass - - -class VerifyAgent670(VerifyAgent): - pass - - -class VerifyAgent671(VerifyAgent439): - pass - - -class VerificationItem335(VerificationItem): - pass - - -class DeclaredBy336(DeclaredBy): - pass - - -class VerifyAgent672(VerifyAgent): - pass - - -class VerifyAgent673(VerifyAgent439): - pass - - -class VerificationItem336(VerificationItem): - pass - - -class DeclaredBy337(DeclaredBy): - pass - - -class VerifyAgent674(VerifyAgent): - pass - - -class VerifyAgent675(VerifyAgent439): - pass - - -class VerificationItem337(VerificationItem): - pass - - -class ReferenceAsset11(ReferenceAsset): - pass - - -class FeedFieldMapping12(FeedFieldMapping): - pass - - -class ReferenceAuthorization10(ReferenceAuthorization): - pass - - -class DeclaredBy338(DeclaredBy): - pass - - -class VerifyAgent676(VerifyAgent): - pass - - -class VerifyAgent677(VerifyAgent439): - pass - - -class VerificationItem338(VerificationItem): - pass - - -class DeclaredBy339(DeclaredBy): - pass - - -class VerifyAgent678(VerifyAgent): - pass - - -class VerifyAgent679(VerifyAgent439): - pass - - -class VerificationItem339(VerificationItem): - pass - - -class DeclaredBy340(DeclaredBy): - pass - - -class VerifyAgent680(VerifyAgent): - pass - - -class VerifyAgent681(VerifyAgent439): - pass - - -class VerificationItem340(VerificationItem): - pass - - -class DeclaredBy341(DeclaredBy): - pass - - -class VerifyAgent682(VerifyAgent): - pass - - -class VerifyAgent683(VerifyAgent439): - pass - - -class VerificationItem341(VerificationItem): - pass - - -class DeclaredBy342(DeclaredBy): - pass - - -class VerifyAgent684(VerifyAgent): - pass - - -class VerifyAgent685(VerifyAgent439): - pass - - -class VerificationItem342(VerificationItem): - pass - - -class DeclaredBy343(DeclaredBy): - pass - - -class VerifyAgent686(VerifyAgent): - pass - - -class VerifyAgent687(VerifyAgent439): - pass - - -class VerificationItem343(VerificationItem): - pass - - -class DeclaredBy344(DeclaredBy): - pass - - -class VerifyAgent688(VerifyAgent): - pass - - -class VerifyAgent689(VerifyAgent439): - pass - - -class VerificationItem344(VerificationItem): - pass - - -class DeclaredBy345(DeclaredBy): - pass - - -class VerifyAgent690(VerifyAgent): - pass - - -class VerifyAgent691(VerifyAgent439): - pass - - -class VerificationItem345(VerificationItem): - pass - - -class DeclaredBy346(DeclaredBy): - pass - - -class VerifyAgent692(VerifyAgent): - pass - - -class VerifyAgent693(VerifyAgent439): - pass - - -class VerificationItem346(VerificationItem): - pass - - -class DeclaredBy347(DeclaredBy): - pass - - -class VerifyAgent694(VerifyAgent): - pass - - -class VerifyAgent695(VerifyAgent439): - pass - - -class VerificationItem347(VerificationItem): - pass - - -class DeclaredBy348(DeclaredBy): - pass - - -class VerifyAgent696(VerifyAgent): - pass - - -class VerifyAgent697(VerifyAgent439): - pass - - -class VerificationItem348(VerificationItem): - pass - - -class DeclaredBy349(DeclaredBy): - pass - - -class VerifyAgent698(VerifyAgent): - pass - - -class VerifyAgent699(VerifyAgent439): - pass - - -class VerificationItem349(VerificationItem): - pass - - -class DeclaredBy350(DeclaredBy): - pass - - -class VerifyAgent700(VerifyAgent): - pass - - -class VerifyAgent701(VerifyAgent439): - pass - - -class VerificationItem350(VerificationItem): - pass - - -class DeclaredBy351(DeclaredBy): - pass - - -class VerifyAgent702(VerifyAgent): - pass - - -class VerifyAgent703(VerifyAgent439): - pass - - -class VerificationItem351(VerificationItem): - pass - - -class DeclaredBy352(DeclaredBy): - pass - - -class VerifyAgent704(VerifyAgent): - pass - - -class VerifyAgent705(VerifyAgent439): - pass - - -class VerificationItem352(VerificationItem): - pass - - -class DeclaredBy353(DeclaredBy): - pass - - -class VerifyAgent706(VerifyAgent): - pass - - -class VerifyAgent707(VerifyAgent439): - pass - - -class VerificationItem353(VerificationItem): - pass - - -class DeclaredBy354(DeclaredBy): - pass - - -class VerifyAgent708(VerifyAgent): - pass - - -class VerifyAgent709(VerifyAgent439): - pass - - -class VerificationItem354(VerificationItem): - pass - - -class DeclaredBy355(DeclaredBy): - pass - - -class VerifyAgent710(VerifyAgent): - pass - - -class VerifyAgent711(VerifyAgent439): - pass - - -class VerificationItem355(VerificationItem): - pass - - -class DeclaredBy356(DeclaredBy): - pass - - -class VerifyAgent712(VerifyAgent): - pass - - -class VerifyAgent713(VerifyAgent439): - pass - - -class VerificationItem356(VerificationItem): - pass - - -class DeclaredBy357(DeclaredBy): - pass - - -class VerifyAgent714(VerifyAgent): - pass - - -class VerifyAgent715(VerifyAgent439): - pass - - -class VerificationItem357(VerificationItem): - pass - - -class DeclaredBy358(DeclaredBy): - pass - - -class VerifyAgent716(VerifyAgent): - pass - - -class VerifyAgent717(VerifyAgent439): - pass - - -class VerificationItem358(VerificationItem): - pass - - -class DeclaredBy359(DeclaredBy): - pass - - -class VerifyAgent718(VerifyAgent): - pass - - -class VerifyAgent719(VerifyAgent439): - pass - - -class VerificationItem359(VerificationItem): - pass - - -class ReferenceAsset12(ReferenceAsset): - pass - - -class FeedFieldMapping13(FeedFieldMapping): - pass - - -class ReferenceAuthorization11(ReferenceAuthorization): - pass - - -class DeclaredBy360(DeclaredBy): - pass - - -class VerifyAgent720(VerifyAgent): - pass - - -class VerifyAgent721(VerifyAgent439): - pass - - -class VerificationItem360(VerificationItem): - pass - - -class DeclaredBy361(DeclaredBy): - pass - - -class VerifyAgent722(VerifyAgent): - pass - - -class VerifyAgent723(VerifyAgent439): - pass - - -class VerificationItem361(VerificationItem): - pass - - -class DeclaredBy362(DeclaredBy): - pass - - -class VerifyAgent724(VerifyAgent): - pass - - -class VerifyAgent725(VerifyAgent439): - pass - - -class VerificationItem362(VerificationItem): - pass - - -class DeclaredBy363(DeclaredBy): - pass - - -class VerifyAgent726(VerifyAgent): - pass - - -class VerifyAgent727(VerifyAgent439): - pass - - -class VerificationItem363(VerificationItem): - pass - - -class DeclaredBy364(DeclaredBy): - pass - - -class VerifyAgent728(VerifyAgent): - pass - - -class VerifyAgent729(VerifyAgent439): - pass - - -class VerificationItem364(VerificationItem): - pass - - -class DeclaredBy365(DeclaredBy): - pass - - -class VerifyAgent730(VerifyAgent): - pass - - -class VerifyAgent731(VerifyAgent439): - pass - - -class VerificationItem365(VerificationItem): - pass - - -class DeclaredBy366(DeclaredBy): - pass - - -class VerifyAgent732(VerifyAgent): - pass - - -class VerifyAgent733(VerifyAgent439): - pass - - -class VerificationItem366(VerificationItem): - pass - - -class DeclaredBy367(DeclaredBy): - pass - - -class VerifyAgent734(VerifyAgent): - pass - - -class VerifyAgent735(VerifyAgent439): - pass - - -class VerificationItem367(VerificationItem): - pass - - -class DeclaredBy368(DeclaredBy): - pass - - -class VerifyAgent736(VerifyAgent): - pass - - -class VerifyAgent737(VerifyAgent439): - pass - - -class VerificationItem368(VerificationItem): - pass - - -class DeclaredBy369(DeclaredBy): - pass - - -class VerifyAgent738(VerifyAgent): - pass - - -class VerifyAgent739(VerifyAgent439): - pass - - -class VerificationItem369(VerificationItem): - pass - - -class Dimensions50(AdCPBaseModel): - width: Annotated[float, Field(ge=0.0)] - height: Annotated[float, Field(ge=0.0)] - - -class Embedding(AdCPBaseModel): - recommended_sandbox: Annotated[ - str | None, - Field( - description="Recommended iframe sandbox attribute value (e.g., 'allow-scripts allow-same-origin')" - ), - ] = None - requires_https: Annotated[ - bool | None, Field(description='Whether this output requires HTTPS for secure embedding') - ] = None - supports_fullscreen: Annotated[ - bool | None, Field(description='Whether this output supports fullscreen mode') - ] = None - csp_policy: Annotated[ - str | None, Field(description='Content Security Policy requirements for embedding') - ] = None - - -class Renders(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['url'], Field(description='Discriminator indicating preview_url is provided') - ] = 'url' - preview_url: Annotated[ - AnyUrl, - Field( - description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions50 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - - -class Renders4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['html'], Field(description='Discriminator indicating preview_html is provided') - ] = 'html' - preview_html: Annotated[ - str, - Field( - description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions50 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, Field(description='Optional security and embedding metadata') - ] = None - - -class Renders5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - render_id: Annotated[ - str, Field(description='Unique identifier for this rendered piece within the variant') - ] - output_format: Annotated[ - Literal['both'], - Field( - description='Discriminator indicating both preview_url and preview_html are provided' - ), - ] = 'both' - preview_url: Annotated[ - AnyUrl, - Field( - description='URL to an HTML page that renders this piece. Can be embedded in an iframe.' - ), - ] - preview_html: Annotated[ - str, - Field( - description='Raw HTML for this rendered piece. Can be embedded directly in the page without iframe. Security warning: Only use with trusted creative agents as this bypasses iframe sandboxing.' - ), - ] - role: Annotated[ - str, - Field( - description="Semantic role of this rendered piece. Use 'primary' for main content, 'companion' for associated banners, descriptive strings for device variants or custom roles." - ), - ] - dimensions: Annotated[ - Dimensions50 | None, Field(description='Dimensions for this rendered piece') - ] = None - embedding: Annotated[ - Embedding | None, - Field(description='Optional security and embedding metadata for safe iframe integration'), - ] = None - - -class Renders2(RootModel[Renders | Renders4 | Renders5]): - root: Annotated[ - Renders | Renders4 | Renders5, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Input(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this variant')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values applied to this variant') - ] = None - context_description: Annotated[ - str | None, Field(description='Context description applied to this variant') - ] = None - - -class Preview1(AdCPBaseModel): - preview_id: Annotated[str, Field(description='Unique identifier for this preview variant')] - renders: Annotated[ - list[Renders2], - Field( - description='Array of rendered pieces for this preview variant. Most formats render as a single piece. Companion ad formats render as multiple pieces.', - min_length=1, - ), - ] - input: Annotated[ - Input, - Field( - description='The input parameters that generated this preview variant. Echoes back the request input or shows defaults used.' - ), - ] - - -class Preview(AdCPBaseModel): - previews: Annotated[ - list[Preview1], - Field( - description='Array of preview variants. Each preview corresponds to an input set from preview_inputs, or a single default preview if no inputs were provided.', - min_length=1, - ), - ] - interactive_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to an interactive testing page that shows all preview variants with controls to switch between them.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when preview URLs expire. May differ from the manifest's expires_at." - ), - ] - - -class Issue42(Issue): - pass - - -class PreviewError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue42] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Consumption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - tokens: Annotated[ - int | None, - Field(description='LLM or generation tokens consumed during creative generation.', ge=0), - ] = None - images_generated: Annotated[ - int | None, Field(description='Number of images produced during generation.', ge=0) - ] = None - renders: Annotated[ - int | None, Field(description='Number of render passes performed (video, animation).', ge=0) - ] = None - duration_seconds: Annotated[ - float | None, - Field( - description='Processing time billed, in seconds. For compute-time pricing models.', - ge=0.0, - ), - ] = None - - -class Issue43(Issue): - pass - - -class AdcpError22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue43] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - - -class DeclaredBy370(DeclaredBy): - pass - - -class VerifyAgent740(VerifyAgent): - pass - - -class VerifyAgent741(VerifyAgent439): - pass - - -class VerificationItem370(VerificationItem): - pass - - -class DeclaredBy371(DeclaredBy): - pass - - -class VerifyAgent742(VerifyAgent): - pass - - -class VerifyAgent743(VerifyAgent439): - pass - - -class VerificationItem371(VerificationItem): - pass - - -class DeclaredBy372(DeclaredBy): - pass - - -class VerifyAgent744(VerifyAgent): - pass - - -class VerifyAgent745(VerifyAgent439): - pass - - -class VerificationItem372(VerificationItem): - pass - - -class DeclaredBy373(DeclaredBy): - pass - - -class VerifyAgent746(VerifyAgent): - pass - - -class VerifyAgent747(VerifyAgent439): - pass - - -class VerificationItem373(VerificationItem): - pass - - -class DeclaredBy374(DeclaredBy): - pass - - -class VerifyAgent748(VerifyAgent): - pass - - -class VerifyAgent749(VerifyAgent439): - pass - - -class VerificationItem374(VerificationItem): - pass - - -class DeclaredBy375(DeclaredBy): - pass - - -class VerifyAgent750(VerifyAgent): - pass - - -class VerifyAgent751(VerifyAgent439): - pass - - -class VerificationItem375(VerificationItem): - pass - - -class DeclaredBy376(DeclaredBy): - pass - - -class VerifyAgent752(VerifyAgent): - pass - - -class VerifyAgent753(VerifyAgent439): - pass - - -class VerificationItem376(VerificationItem): - pass - - -class DeclaredBy377(DeclaredBy): - pass - - -class VerifyAgent754(VerifyAgent): - pass - - -class VerifyAgent755(VerifyAgent439): - pass - - -class VerificationItem377(VerificationItem): - pass - - -class DeclaredBy378(DeclaredBy): - pass - - -class VerifyAgent756(VerifyAgent): - pass - - -class VerifyAgent757(VerifyAgent439): - pass - - -class VerificationItem378(VerificationItem): - pass - - -class DeclaredBy379(DeclaredBy): - pass - - -class VerifyAgent758(VerifyAgent): - pass - - -class VerifyAgent759(VerifyAgent439): - pass - - -class VerificationItem379(VerificationItem): - pass - - -class DeclaredBy380(DeclaredBy): - pass - - -class VerifyAgent760(VerifyAgent): - pass - - -class VerifyAgent761(VerifyAgent439): - pass - - -class VerificationItem380(VerificationItem): - pass - - -class DeclaredBy381(DeclaredBy): - pass - - -class VerifyAgent762(VerifyAgent): - pass - - -class VerifyAgent763(VerifyAgent439): - pass - - -class VerificationItem381(VerificationItem): - pass - - -class DeclaredBy382(DeclaredBy): - pass - - -class VerifyAgent764(VerifyAgent): - pass - - -class VerifyAgent765(VerifyAgent439): - pass - - -class VerificationItem382(VerificationItem): - pass - - -class DeclaredBy383(DeclaredBy): - pass - - -class VerifyAgent766(VerifyAgent): - pass - - -class VerifyAgent767(VerifyAgent439): - pass - - -class VerificationItem383(VerificationItem): - pass - - -class ReferenceAsset13(ReferenceAsset): - pass - - -class FeedFieldMapping14(FeedFieldMapping): - pass - - -class ReferenceAuthorization12(ReferenceAuthorization): - pass - - -class DeclaredBy384(DeclaredBy): - pass - - -class VerifyAgent768(VerifyAgent): - pass - - -class VerifyAgent769(VerifyAgent439): - pass - - -class VerificationItem384(VerificationItem): - pass - - -class DeclaredBy385(DeclaredBy): - pass - - -class VerifyAgent770(VerifyAgent): - pass - - -class VerifyAgent771(VerifyAgent439): - pass - - -class VerificationItem385(VerificationItem): - pass - - -class DeclaredBy386(DeclaredBy): - pass - - -class VerifyAgent772(VerifyAgent): - pass - - -class VerifyAgent773(VerifyAgent439): - pass - - -class VerificationItem386(VerificationItem): - pass - - -class DeclaredBy387(DeclaredBy): - pass - - -class VerifyAgent774(VerifyAgent): - pass - - -class VerifyAgent775(VerifyAgent439): - pass - - -class VerificationItem387(VerificationItem): - pass - - -class DeclaredBy388(DeclaredBy): - pass - - -class VerifyAgent776(VerifyAgent): - pass - - -class VerifyAgent777(VerifyAgent439): - pass - - -class VerificationItem388(VerificationItem): - pass - - -class DeclaredBy389(DeclaredBy): - pass - - -class VerifyAgent778(VerifyAgent): - pass - - -class VerifyAgent779(VerifyAgent439): - pass - - -class VerificationItem389(VerificationItem): - pass - - -class DeclaredBy390(DeclaredBy): - pass - - -class VerifyAgent780(VerifyAgent): - pass - - -class VerifyAgent781(VerifyAgent439): - pass - - -class VerificationItem390(VerificationItem): - pass - - -class DeclaredBy391(DeclaredBy): - pass - - -class VerifyAgent782(VerifyAgent): - pass - - -class VerifyAgent783(VerifyAgent439): - pass - - -class VerificationItem391(VerificationItem): - pass - - -class DeclaredBy392(DeclaredBy): - pass - - -class VerifyAgent784(VerifyAgent): - pass - - -class VerifyAgent785(VerifyAgent439): - pass - - -class VerificationItem392(VerificationItem): - pass - - -class DeclaredBy393(DeclaredBy): - pass - - -class VerifyAgent786(VerifyAgent): - pass - - -class VerifyAgent787(VerifyAgent439): - pass - - -class VerificationItem393(VerificationItem): - pass - - -class DeclaredBy394(DeclaredBy): - pass - - -class VerifyAgent788(VerifyAgent): - pass - - -class VerifyAgent789(VerifyAgent439): - pass - - -class VerificationItem394(VerificationItem): - pass - - -class DeclaredBy395(DeclaredBy): - pass - - -class VerifyAgent790(VerifyAgent): - pass - - -class VerifyAgent791(VerifyAgent439): - pass - - -class VerificationItem395(VerificationItem): - pass - - -class DeclaredBy396(DeclaredBy): - pass - - -class VerifyAgent792(VerifyAgent): - pass - - -class VerifyAgent793(VerifyAgent439): - pass - - -class VerificationItem396(VerificationItem): - pass - - -class DeclaredBy397(DeclaredBy): - pass - - -class VerifyAgent794(VerifyAgent): - pass - - -class VerifyAgent795(VerifyAgent439): - pass - - -class VerificationItem397(VerificationItem): - pass - - -class DeclaredBy398(DeclaredBy): - pass - - -class VerifyAgent796(VerifyAgent): - pass - - -class VerifyAgent797(VerifyAgent439): - pass - - -class VerificationItem398(VerificationItem): - pass - - -class DeclaredBy399(DeclaredBy): - pass - - -class VerifyAgent798(VerifyAgent): - pass - - -class VerifyAgent799(VerifyAgent439): - pass - - -class VerificationItem399(VerificationItem): - pass - - -class DeclaredBy400(DeclaredBy): - pass - - -class VerifyAgent800(VerifyAgent): - pass - - -class VerifyAgent801(VerifyAgent439): - pass - - -class VerificationItem400(VerificationItem): - pass - - -class DeclaredBy401(DeclaredBy): - pass - - -class VerifyAgent802(VerifyAgent): - pass - - -class VerifyAgent803(VerifyAgent439): - pass - - -class VerificationItem401(VerificationItem): - pass - - -class DeclaredBy402(DeclaredBy): - pass - - -class VerifyAgent804(VerifyAgent): - pass - - -class VerifyAgent805(VerifyAgent439): - pass - - -class VerificationItem402(VerificationItem): - pass - - -class DeclaredBy403(DeclaredBy): - pass - - -class VerifyAgent806(VerifyAgent): - pass - - -class VerifyAgent807(VerifyAgent439): - pass - - -class VerificationItem403(VerificationItem): - pass - - -class DeclaredBy404(DeclaredBy): - pass - - -class VerifyAgent808(VerifyAgent): - pass - - -class VerifyAgent809(VerifyAgent439): - pass - - -class VerificationItem404(VerificationItem): - pass - - -class DeclaredBy405(DeclaredBy): - pass - - -class VerifyAgent810(VerifyAgent): - pass - - -class VerifyAgent811(VerifyAgent439): - pass - - -class VerificationItem405(VerificationItem): - pass - - -class ReferenceAsset14(ReferenceAsset): - pass - - -class FeedFieldMapping15(FeedFieldMapping): - pass - - -class ReferenceAuthorization13(ReferenceAuthorization): - pass - - -class DeclaredBy406(DeclaredBy): - pass - - -class VerifyAgent812(VerifyAgent): - pass - - -class VerifyAgent813(VerifyAgent439): - pass - - -class VerificationItem406(VerificationItem): - pass - - -class DeclaredBy407(DeclaredBy): - pass - - -class VerifyAgent814(VerifyAgent): - pass - - -class VerifyAgent815(VerifyAgent439): - pass - - -class VerificationItem407(VerificationItem): - pass - - -class DeclaredBy408(DeclaredBy): - pass - - -class VerifyAgent816(VerifyAgent): - pass - - -class VerifyAgent817(VerifyAgent439): - pass - - -class VerificationItem408(VerificationItem): - pass - - -class DeclaredBy409(DeclaredBy): - pass - - -class VerifyAgent818(VerifyAgent): - pass - - -class VerifyAgent819(VerifyAgent439): - pass - - -class VerificationItem409(VerificationItem): - pass - - -class DeclaredBy410(DeclaredBy): - pass - - -class VerifyAgent820(VerifyAgent): - pass - - -class VerifyAgent821(VerifyAgent439): - pass - - -class VerificationItem410(VerificationItem): - pass - - -class DeclaredBy411(DeclaredBy): - pass - - -class VerifyAgent822(VerifyAgent): - pass - - -class VerifyAgent823(VerifyAgent439): - pass - - -class VerificationItem411(VerificationItem): - pass - - -class DeclaredBy412(DeclaredBy): - pass - - -class VerifyAgent824(VerifyAgent): - pass - - -class VerifyAgent825(VerifyAgent439): - pass - - -class VerificationItem412(VerificationItem): - pass - - -class DeclaredBy413(DeclaredBy): - pass - - -class VerifyAgent826(VerifyAgent): - pass - - -class VerifyAgent827(VerifyAgent439): - pass - - -class VerificationItem413(VerificationItem): - pass - - -class DeclaredBy414(DeclaredBy): - pass - - -class VerifyAgent828(VerifyAgent): - pass - - -class VerifyAgent829(VerifyAgent439): - pass - - -class VerificationItem414(VerificationItem): - pass - - -class DeclaredBy415(DeclaredBy): - pass - - -class VerifyAgent830(VerifyAgent): - pass - - -class VerifyAgent831(VerifyAgent439): - pass - - -class VerificationItem415(VerificationItem): - pass - - - -class DeclaredBy416(DeclaredBy): - pass - - -class VerifyAgent832(VerifyAgent): - pass - - -class VerifyAgent833(VerifyAgent439): - pass - - -class VerificationItem416(VerificationItem): - pass - - -class DeclaredBy417(DeclaredBy): - pass - - -class VerifyAgent834(VerifyAgent): - pass - - -class VerifyAgent835(VerifyAgent439): - pass - - -class VerificationItem417(VerificationItem): - pass - - -class DeclaredBy418(DeclaredBy): - pass - - -class VerifyAgent836(VerifyAgent): - pass - - -class VerifyAgent837(VerifyAgent439): - pass - - -class VerificationItem418(VerificationItem): - pass - - -class DeclaredBy419(DeclaredBy): - pass - - -class VerifyAgent838(VerifyAgent): - pass - - -class VerifyAgent839(VerifyAgent439): - pass - - -class VerificationItem419(VerificationItem): - pass - - -class DeclaredBy420(DeclaredBy): - pass - - -class VerifyAgent840(VerifyAgent): - pass - - -class VerifyAgent841(VerifyAgent439): - pass - - -class VerificationItem420(VerificationItem): - pass - - -class DeclaredBy421(DeclaredBy): - pass - - -class VerifyAgent842(VerifyAgent): - pass - - -class VerifyAgent843(VerifyAgent439): - pass - - -class VerificationItem421(VerificationItem): - pass - - -class DeclaredBy422(DeclaredBy): - pass - - -class VerifyAgent844(VerifyAgent): - pass - - -class VerifyAgent845(VerifyAgent439): - pass - - -class VerificationItem422(VerificationItem): - pass - - -class DeclaredBy423(DeclaredBy): - pass - - -class VerifyAgent846(VerifyAgent): - pass - - -class VerifyAgent847(VerifyAgent439): - pass - - -class VerificationItem423(VerificationItem): - pass - - -class DeclaredBy424(DeclaredBy): - pass - - -class VerifyAgent848(VerifyAgent): - pass - - -class VerifyAgent849(VerifyAgent439): - pass - - -class VerificationItem424(VerificationItem): - pass - - -class DeclaredBy425(DeclaredBy): - pass - - -class VerifyAgent850(VerifyAgent): - pass - - -class VerifyAgent851(VerifyAgent439): - pass - - -class VerificationItem425(VerificationItem): - pass - - -class DeclaredBy426(DeclaredBy): - pass - - -class VerifyAgent852(VerifyAgent): - pass - - -class VerifyAgent853(VerifyAgent439): - pass - - -class VerificationItem426(VerificationItem): - pass - - -class DeclaredBy427(DeclaredBy): - pass - - -class VerifyAgent854(VerifyAgent): - pass - - -class VerifyAgent855(VerifyAgent439): - pass - - -class VerificationItem427(VerificationItem): - pass - - -class DeclaredBy428(DeclaredBy): - pass - - -class VerifyAgent856(VerifyAgent): - pass - - -class VerifyAgent857(VerifyAgent439): - pass - - -class VerificationItem428(VerificationItem): - pass - - -class DeclaredBy429(DeclaredBy): - pass - - -class VerifyAgent858(VerifyAgent): - pass - - -class VerifyAgent859(VerifyAgent439): - pass - - -class VerificationItem429(VerificationItem): - pass - - -class ReferenceAsset15(ReferenceAsset): - pass - - -class FeedFieldMapping16(FeedFieldMapping): - pass - - -class ReferenceAuthorization14(ReferenceAuthorization): - pass - - -class DeclaredBy430(DeclaredBy): - pass - - -class VerifyAgent860(VerifyAgent): - pass - - -class VerifyAgent861(VerifyAgent439): - pass - - -class VerificationItem430(VerificationItem): - pass - - -class DeclaredBy431(DeclaredBy): - pass - - -class VerifyAgent862(VerifyAgent): - pass - - -class VerifyAgent863(VerifyAgent439): - pass - - -class VerificationItem431(VerificationItem): - pass - - -class DeclaredBy432(DeclaredBy): - pass - - -class VerifyAgent864(VerifyAgent): - pass - - -class VerifyAgent865(VerifyAgent439): - pass - - -class VerificationItem432(VerificationItem): - pass - - -class DeclaredBy433(DeclaredBy): - pass - - -class VerifyAgent866(VerifyAgent): - pass - - -class VerifyAgent867(VerifyAgent439): - pass - - -class VerificationItem433(VerificationItem): - pass - - -class DeclaredBy434(DeclaredBy): - pass - - -class VerifyAgent868(VerifyAgent): - pass - - -class VerifyAgent869(VerifyAgent439): - pass - - -class VerificationItem434(VerificationItem): - pass - - -class DeclaredBy435(DeclaredBy): - pass - - -class VerifyAgent870(VerifyAgent): - pass - - -class VerifyAgent871(VerifyAgent439): - pass - - -class VerificationItem435(VerificationItem): - pass - - -class DeclaredBy436(DeclaredBy): - pass - - -class VerifyAgent872(VerifyAgent): - pass - - -class VerifyAgent873(VerifyAgent439): - pass - - -class VerificationItem436(VerificationItem): - pass - - -class DeclaredBy437(DeclaredBy): - pass - - -class VerifyAgent874(VerifyAgent): - pass - - -class VerifyAgent875(VerifyAgent439): - pass - - -class VerificationItem437(VerificationItem): - pass - - -class DeclaredBy438(DeclaredBy): - pass - - -class VerifyAgent876(VerifyAgent): - pass - - -class VerifyAgent877(VerifyAgent439): - pass - - -class VerificationItem438(VerificationItem): - pass - - -class DeclaredBy439(DeclaredBy): - pass - - -class VerifyAgent878(VerifyAgent): - pass - - -class VerifyAgent879(VerifyAgent439): - pass - - -class VerificationItem439(VerificationItem): - pass - - -class DeclaredBy440(DeclaredBy): - pass - - -class VerifyAgent880(VerifyAgent): - pass - - -class VerifyAgent881(VerifyAgent439): - pass - - -class VerificationItem440(VerificationItem): - pass - - -class DeclaredBy441(DeclaredBy): - pass - - -class VerifyAgent882(VerifyAgent): - pass - - -class VerifyAgent883(VerifyAgent439): - pass - - -class VerificationItem441(VerificationItem): - pass - - -class DeclaredBy442(DeclaredBy): - pass - - -class VerifyAgent884(VerifyAgent): - pass - - -class VerifyAgent885(VerifyAgent439): - pass - - -class VerificationItem442(VerificationItem): - pass - - -class DeclaredBy443(DeclaredBy): - pass - - -class VerifyAgent886(VerifyAgent): - pass - - -class VerifyAgent887(VerifyAgent439): - pass - - -class VerificationItem443(VerificationItem): - pass - - -class DeclaredBy444(DeclaredBy): - pass - - -class VerifyAgent888(VerifyAgent): - pass - - -class VerifyAgent889(VerifyAgent439): - pass - - -class VerificationItem444(VerificationItem): - pass - - -class DeclaredBy445(DeclaredBy): - pass - - -class VerifyAgent890(VerifyAgent): - pass - - -class VerifyAgent891(VerifyAgent439): - pass - - -class VerificationItem445(VerificationItem): - pass - - -class DeclaredBy446(DeclaredBy): - pass - - -class VerifyAgent892(VerifyAgent): - pass - - -class VerifyAgent893(VerifyAgent439): - pass - - -class VerificationItem446(VerificationItem): - pass - - -class DeclaredBy447(DeclaredBy): - pass - - -class VerifyAgent894(VerifyAgent): - pass - - -class VerifyAgent895(VerifyAgent439): - pass - - -class VerificationItem447(VerificationItem): - pass - - -class DeclaredBy448(DeclaredBy): - pass - - -class VerifyAgent896(VerifyAgent): - pass - - -class VerifyAgent897(VerifyAgent439): - pass - - -class VerificationItem448(VerificationItem): - pass - - -class DeclaredBy449(DeclaredBy): - pass - - -class VerifyAgent898(VerifyAgent): - pass - - -class VerifyAgent899(VerifyAgent439): - pass - - -class VerificationItem449(VerificationItem): - pass - - -class DeclaredBy450(DeclaredBy): - pass - - -class VerifyAgent900(VerifyAgent): - pass - - -class VerifyAgent901(VerifyAgent439): - pass - - -class VerificationItem450(VerificationItem): - pass - - -class DeclaredBy451(DeclaredBy): - pass - - -class VerifyAgent902(VerifyAgent): - pass - - -class VerifyAgent903(VerifyAgent439): - pass - - -class VerificationItem451(VerificationItem): - pass - - -class ReferenceAsset16(ReferenceAsset): - pass - - -class FeedFieldMapping17(FeedFieldMapping): - pass - - -class ReferenceAuthorization15(ReferenceAuthorization): - pass - - -class DeclaredBy452(DeclaredBy): - pass - - -class VerifyAgent904(VerifyAgent): - pass - - -class VerifyAgent905(VerifyAgent439): - pass - - -class VerificationItem452(VerificationItem): - pass - - -class DeclaredBy453(DeclaredBy): - pass - - -class VerifyAgent906(VerifyAgent): - pass - - -class VerifyAgent907(VerifyAgent439): - pass - - -class VerificationItem453(VerificationItem): - pass - - -class DeclaredBy454(DeclaredBy): - pass - - -class VerifyAgent908(VerifyAgent): - pass - - -class VerifyAgent909(VerifyAgent439): - pass - - -class VerificationItem454(VerificationItem): - pass - - -class DeclaredBy455(DeclaredBy): - pass - - -class VerifyAgent910(VerifyAgent): - pass - - -class VerifyAgent911(VerifyAgent439): - pass - - -class VerificationItem455(VerificationItem): - pass - - -class DeclaredBy456(DeclaredBy): - pass - - -class VerifyAgent912(VerifyAgent): - pass - - -class VerifyAgent913(VerifyAgent439): - pass - - -class VerificationItem456(VerificationItem): - pass - - -class DeclaredBy457(DeclaredBy): - pass - - -class VerifyAgent914(VerifyAgent): - pass - - -class VerifyAgent915(VerifyAgent439): - pass - - -class VerificationItem457(VerificationItem): - pass - - -class DeclaredBy458(DeclaredBy): - pass - - -class VerifyAgent916(VerifyAgent): - pass - - -class VerifyAgent917(VerifyAgent439): - pass - - -class VerificationItem458(VerificationItem): - pass - - -class DeclaredBy459(DeclaredBy): - pass - - -class VerifyAgent918(VerifyAgent): - pass - - -class VerifyAgent919(VerifyAgent439): - pass - - -class VerificationItem459(VerificationItem): - pass - - -class DeclaredBy460(DeclaredBy): - pass - - -class VerifyAgent920(VerifyAgent): - pass - - -class VerifyAgent921(VerifyAgent439): - pass - - -class VerificationItem460(VerificationItem): - pass - - -class DeclaredBy461(DeclaredBy): - pass - - -class VerifyAgent922(VerifyAgent): - pass - - -class VerifyAgent923(VerifyAgent439): - pass - - -class VerificationItem461(VerificationItem): - pass - - - - -class Renders6(RootModel[Renders | Renders4 | Renders5]): - root: Annotated[ - Renders | Renders4 | Renders5, - Field( - description='A single rendered piece of a creative preview with discriminated output format', - discriminator='output_format', - title='Preview Render', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Input3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[str, Field(description='Human-readable name for this preview')] - macros: Annotated[ - dict[str, str] | None, Field(description='Macro values applied to this preview') - ] = None - context_description: Annotated[ - str | None, Field(description='Context description applied to this preview') - ] = None - - -class Preview3(AdCPBaseModel): - preview_id: Annotated[str, Field(description='Unique identifier for this preview')] - format_id: Annotated[ - FormatId, - Field( - description='The format this preview was generated for. Matches one of the requested target_format_ids.', - title='Format Reference (Structured Object)', - ), - ] - renders: Annotated[ - list[Renders6], - Field( - description="Array of rendered pieces for this format's preview. Most formats render as a single piece. Companion ad formats render as multiple pieces.", - min_length=1, - ), - ] - input: Annotated[ - Input3, - Field( - description='The input parameters that generated this preview. For multi-format responses, this is always a default input.' - ), - ] - - -class Preview2(AdCPBaseModel): - previews: Annotated[ - list[Preview3], - Field( - description='Array of preview entries, one per requested format. Array order matches creative_manifests. Each entry includes a format_id for explicit correlation.', - min_length=1, - ), - ] - interactive_url: Annotated[ - AnyUrl | None, - Field( - description='Optional URL to an interactive testing page that shows all format previews with controls to switch between them.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime, - Field( - description="ISO 8601 timestamp when preview URLs expire. May differ from the manifest's expires_at." - ), - ] - - -class Issue44(Issue): - pass - - -class PreviewError1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue44] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue45(Issue): - pass - - -class AdcpError23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue45] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class CatalogItemRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_type: Annotated[ - str | None, Field(description='The catalog type the item came from.') - ] = None - item_id: Annotated[ - str, Field(description='Identifier of the catalog item this creative was built for.') - ] - - - - - - -class SignalCondition(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[Literal['binary'], Field(description='Discriminator for binary signals')] = 'binary' - value: Annotated[ - bool, - Field( - description='Whether to include (true) or exclude (false) users matching this signal' - ), - ] - - - - - - -class SignalCondition9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['categorical'], Field(description='Discriminator for categorical signals') - ] = 'categorical' - values: Annotated[ - list[str], - Field( - description='Values to target. Users with any of these values will be included.', - min_length=1, - ), - ] - - - - - - -class SignalCondition10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description='The signal to target. New targeting constraints SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - value_type: Annotated[ - Literal['numeric'], Field(description='Discriminator for numeric signals') - ] = 'numeric' - min_value: Annotated[ - float | None, - Field( - description="Minimum value (inclusive). Omit for no minimum. Must be <= max_value when both are provided. Should be >= signal's range.min if defined." - ), - ] = None - max_value: Annotated[ - float | None, - Field( - description="Maximum value (inclusive). Omit for no maximum. Must be >= min_value when both are provided. Should be <= signal's range.max if defined." - ), - ] = None - - - -class DeclaredBy462(DeclaredBy): - pass - - -class VerifyAgent924(VerifyAgent): - pass - - -class VerifyAgent925(VerifyAgent439): - pass - - -class VerificationItem462(VerificationItem): - pass - - -class DeclaredBy463(DeclaredBy): - pass - - -class VerifyAgent926(VerifyAgent): - pass - - -class VerifyAgent927(VerifyAgent439): - pass - - -class VerificationItem463(VerificationItem): - pass - - -class DeclaredBy464(DeclaredBy): - pass - - -class VerifyAgent928(VerifyAgent): - pass - - -class VerifyAgent929(VerifyAgent439): - pass - - -class VerificationItem464(VerificationItem): - pass - - -class DeclaredBy465(DeclaredBy): - pass - - -class VerifyAgent930(VerifyAgent): - pass - - -class VerifyAgent931(VerifyAgent439): - pass - - -class VerificationItem465(VerificationItem): - pass - - -class DeclaredBy466(DeclaredBy): - pass - - -class VerifyAgent932(VerifyAgent): - pass - - -class VerifyAgent933(VerifyAgent439): - pass - - -class VerificationItem466(VerificationItem): - pass - - -class DeclaredBy467(DeclaredBy): - pass - - -class VerifyAgent934(VerifyAgent): - pass - - -class VerifyAgent935(VerifyAgent439): - pass - - -class VerificationItem467(VerificationItem): - pass - - -class DeclaredBy468(DeclaredBy): - pass - - -class VerifyAgent936(VerifyAgent): - pass - - -class VerifyAgent937(VerifyAgent439): - pass - - -class VerificationItem468(VerificationItem): - pass - - -class DeclaredBy469(DeclaredBy): - pass - - -class VerifyAgent938(VerifyAgent): - pass - - -class VerifyAgent939(VerifyAgent439): - pass - - -class VerificationItem469(VerificationItem): - pass - - -class DeclaredBy470(DeclaredBy): - pass - - -class VerifyAgent940(VerifyAgent): - pass - - -class VerifyAgent941(VerifyAgent439): - pass - - -class VerificationItem470(VerificationItem): - pass - - -class DeclaredBy471(DeclaredBy): - pass - - -class VerifyAgent942(VerifyAgent): - pass - - -class VerifyAgent943(VerifyAgent439): - pass - - -class VerificationItem471(VerificationItem): - pass - - -class DeclaredBy472(DeclaredBy): - pass - - -class VerifyAgent944(VerifyAgent): - pass - - -class VerifyAgent945(VerifyAgent439): - pass - - -class VerificationItem472(VerificationItem): - pass - - -class DeclaredBy473(DeclaredBy): - pass - - -class VerifyAgent946(VerifyAgent): - pass - - -class VerifyAgent947(VerifyAgent439): - pass - - -class VerificationItem473(VerificationItem): - pass - - -class DeclaredBy474(DeclaredBy): - pass - - -class VerifyAgent948(VerifyAgent): - pass - - -class VerifyAgent949(VerifyAgent439): - pass - - -class VerificationItem474(VerificationItem): - pass - - -class DeclaredBy475(DeclaredBy): - pass - - -class VerifyAgent950(VerifyAgent): - pass - - -class VerifyAgent951(VerifyAgent439): - pass - - -class VerificationItem475(VerificationItem): - pass - - -class ReferenceAsset17(ReferenceAsset): - pass - - -class FeedFieldMapping18(FeedFieldMapping): - pass - - -class ReferenceAuthorization16(ReferenceAuthorization): - pass - - -class DeclaredBy476(DeclaredBy): - pass - - -class VerifyAgent952(VerifyAgent): - pass - - -class VerifyAgent953(VerifyAgent439): - pass - - -class VerificationItem476(VerificationItem): - pass - - -class DeclaredBy477(DeclaredBy): - pass - - -class VerifyAgent954(VerifyAgent): - pass - - -class VerifyAgent955(VerifyAgent439): - pass - - -class VerificationItem477(VerificationItem): - pass - - -class DeclaredBy478(DeclaredBy): - pass - - -class VerifyAgent956(VerifyAgent): - pass - - -class VerifyAgent957(VerifyAgent439): - pass - - -class VerificationItem478(VerificationItem): - pass - - -class DeclaredBy479(DeclaredBy): - pass - - -class VerifyAgent958(VerifyAgent): - pass - - -class VerifyAgent959(VerifyAgent439): - pass - - -class VerificationItem479(VerificationItem): - pass - - -class DeclaredBy480(DeclaredBy): - pass - - -class VerifyAgent960(VerifyAgent): - pass - - -class VerifyAgent961(VerifyAgent439): - pass - - -class VerificationItem480(VerificationItem): - pass - - -class DeclaredBy481(DeclaredBy): - pass - - -class VerifyAgent962(VerifyAgent): - pass - - -class VerifyAgent963(VerifyAgent439): - pass - - -class VerificationItem481(VerificationItem): - pass - - -class DeclaredBy482(DeclaredBy): - pass - - -class VerifyAgent964(VerifyAgent): - pass - - -class VerifyAgent965(VerifyAgent439): - pass - - -class VerificationItem482(VerificationItem): - pass - - -class DeclaredBy483(DeclaredBy): - pass - - -class VerifyAgent966(VerifyAgent): - pass - - -class VerifyAgent967(VerifyAgent439): - pass - - -class VerificationItem483(VerificationItem): - pass - - -class DeclaredBy484(DeclaredBy): - pass - - -class VerifyAgent968(VerifyAgent): - pass - - -class VerifyAgent969(VerifyAgent439): - pass - - -class VerificationItem484(VerificationItem): - pass - - -class DeclaredBy485(DeclaredBy): - pass - - -class VerifyAgent970(VerifyAgent): - pass - - -class VerifyAgent971(VerifyAgent439): - pass - - -class VerificationItem485(VerificationItem): - pass - - -class DeclaredBy486(DeclaredBy): - pass - - -class VerifyAgent972(VerifyAgent): - pass - - -class VerifyAgent973(VerifyAgent439): - pass - - -class VerificationItem486(VerificationItem): - pass - - -class DeclaredBy487(DeclaredBy): - pass - - -class VerifyAgent974(VerifyAgent): - pass - - -class VerifyAgent975(VerifyAgent439): - pass - - -class VerificationItem487(VerificationItem): - pass - - -class DeclaredBy488(DeclaredBy): - pass - - -class VerifyAgent976(VerifyAgent): - pass - - -class VerifyAgent977(VerifyAgent439): - pass - - -class VerificationItem488(VerificationItem): - pass - - -class DeclaredBy489(DeclaredBy): - pass - - -class VerifyAgent978(VerifyAgent): - pass - - -class VerifyAgent979(VerifyAgent439): - pass - - -class VerificationItem489(VerificationItem): - pass - - -class DeclaredBy490(DeclaredBy): - pass - - -class VerifyAgent980(VerifyAgent): - pass - - -class VerifyAgent981(VerifyAgent439): - pass - - -class VerificationItem490(VerificationItem): - pass - - -class DeclaredBy491(DeclaredBy): - pass - - -class VerifyAgent982(VerifyAgent): - pass - - -class VerifyAgent983(VerifyAgent439): - pass - - -class VerificationItem491(VerificationItem): - pass - - -class DeclaredBy492(DeclaredBy): - pass - - -class VerifyAgent984(VerifyAgent): - pass - - -class VerifyAgent985(VerifyAgent439): - pass - - -class VerificationItem492(VerificationItem): - pass - - -class DeclaredBy493(DeclaredBy): - pass - - -class VerifyAgent986(VerifyAgent): - pass - - -class VerifyAgent987(VerifyAgent439): - pass - - -class VerificationItem493(VerificationItem): - pass - - -class DeclaredBy494(DeclaredBy): - pass - - -class VerifyAgent988(VerifyAgent): - pass - - -class VerifyAgent989(VerifyAgent439): - pass - - -class VerificationItem494(VerificationItem): - pass - - -class DeclaredBy495(DeclaredBy): - pass - - -class VerifyAgent990(VerifyAgent): - pass - - -class VerifyAgent991(VerifyAgent439): - pass - - -class VerificationItem495(VerificationItem): - pass - - -class DeclaredBy496(DeclaredBy): - pass - - -class VerifyAgent992(VerifyAgent): - pass - - -class VerifyAgent993(VerifyAgent439): - pass - - -class VerificationItem496(VerificationItem): - pass - - -class DeclaredBy497(DeclaredBy): - pass - - -class VerifyAgent994(VerifyAgent): - pass - - -class VerifyAgent995(VerifyAgent439): - pass - - -class VerificationItem497(VerificationItem): - pass - - -class ReferenceAsset18(ReferenceAsset): - pass - - -class FeedFieldMapping19(FeedFieldMapping): - pass - - -class ReferenceAuthorization17(ReferenceAuthorization): - pass - - -class DeclaredBy498(DeclaredBy): - pass - - -class VerifyAgent996(VerifyAgent): - pass - - -class VerifyAgent997(VerifyAgent439): - pass - - -class VerificationItem498(VerificationItem): - pass - - -class DeclaredBy499(DeclaredBy): - pass - - -class VerifyAgent998(VerifyAgent): - pass - - -class VerifyAgent999(VerifyAgent439): - pass - - -class VerificationItem499(VerificationItem): - pass - - -class DeclaredBy500(DeclaredBy): - pass - - -class VerifyAgent1000(VerifyAgent): - pass - - -class VerifyAgent1001(VerifyAgent439): - pass - - -class VerificationItem500(VerificationItem): - pass - - -class DeclaredBy501(DeclaredBy): - pass - - -class VerifyAgent1002(VerifyAgent): - pass - - -class VerifyAgent1003(VerifyAgent439): - pass - - -class VerificationItem501(VerificationItem): - pass - - -class DeclaredBy502(DeclaredBy): - pass - - -class VerifyAgent1004(VerifyAgent): - pass - - -class VerifyAgent1005(VerifyAgent439): - pass - - -class VerificationItem502(VerificationItem): - pass - - -class DeclaredBy503(DeclaredBy): - pass - - -class VerifyAgent1006(VerifyAgent): - pass - - -class VerifyAgent1007(VerifyAgent439): - pass - - -class VerificationItem503(VerificationItem): - pass - - -class DeclaredBy504(DeclaredBy): - pass - - -class VerifyAgent1008(VerifyAgent): - pass - - -class VerifyAgent1009(VerifyAgent439): - pass - - -class VerificationItem504(VerificationItem): - pass - - -class DeclaredBy505(DeclaredBy): - pass - - -class VerifyAgent1010(VerifyAgent): - pass - - -class VerifyAgent1011(VerifyAgent439): - pass - - -class VerificationItem505(VerificationItem): - pass - - -class DeclaredBy506(DeclaredBy): - pass - - -class VerifyAgent1012(VerifyAgent): - pass - - -class VerifyAgent1013(VerifyAgent439): - pass - - -class VerificationItem506(VerificationItem): - pass - - -class DeclaredBy507(DeclaredBy): - pass - - -class VerifyAgent1014(VerifyAgent): - pass - - -class VerifyAgent1015(VerifyAgent439): - pass - - -class VerificationItem507(VerificationItem): - pass - - - -class DeclaredBy508(DeclaredBy): - pass - - -class VerifyAgent1016(VerifyAgent): - pass - - -class VerifyAgent1017(VerifyAgent439): - pass - - -class VerificationItem508(VerificationItem): - pass - - -class DeclaredBy509(DeclaredBy): - pass - - -class VerifyAgent1018(VerifyAgent): - pass - - -class VerifyAgent1019(VerifyAgent439): - pass - - -class VerificationItem509(VerificationItem): - pass - - -class DeclaredBy510(DeclaredBy): - pass - - -class VerifyAgent1020(VerifyAgent): - pass - - -class VerifyAgent1021(VerifyAgent439): - pass - - -class VerificationItem510(VerificationItem): - pass - - -class DeclaredBy511(DeclaredBy): - pass - - -class VerifyAgent1022(VerifyAgent): - pass - - -class VerifyAgent1023(VerifyAgent439): - pass - - -class VerificationItem511(VerificationItem): - pass - - -class DeclaredBy512(DeclaredBy): - pass - - -class VerifyAgent1024(VerifyAgent): - pass - - -class VerifyAgent1025(VerifyAgent439): - pass - - -class VerificationItem512(VerificationItem): - pass - - -class DeclaredBy513(DeclaredBy): - pass - - -class VerifyAgent1026(VerifyAgent): - pass - - -class VerifyAgent1027(VerifyAgent439): - pass - - -class VerificationItem513(VerificationItem): - pass - - -class DeclaredBy514(DeclaredBy): - pass - - -class VerifyAgent1028(VerifyAgent): - pass - - -class VerifyAgent1029(VerifyAgent439): - pass - - -class VerificationItem514(VerificationItem): - pass - - -class DeclaredBy515(DeclaredBy): - pass - - -class VerifyAgent1030(VerifyAgent): - pass - - -class VerifyAgent1031(VerifyAgent439): - pass - - -class VerificationItem515(VerificationItem): - pass - - -class DeclaredBy516(DeclaredBy): - pass - - -class VerifyAgent1032(VerifyAgent): - pass - - -class VerifyAgent1033(VerifyAgent439): - pass - - -class VerificationItem516(VerificationItem): - pass - - -class DeclaredBy517(DeclaredBy): - pass - - -class VerifyAgent1034(VerifyAgent): - pass - - -class VerifyAgent1035(VerifyAgent439): - pass - - -class VerificationItem517(VerificationItem): - pass - - -class DeclaredBy518(DeclaredBy): - pass - - -class VerifyAgent1036(VerifyAgent): - pass - - -class VerifyAgent1037(VerifyAgent439): - pass - - -class VerificationItem518(VerificationItem): - pass - - -class DeclaredBy519(DeclaredBy): - pass - - -class VerifyAgent1038(VerifyAgent): - pass - - -class VerifyAgent1039(VerifyAgent439): - pass - - -class VerificationItem519(VerificationItem): - pass - - -class DeclaredBy520(DeclaredBy): - pass - - -class VerifyAgent1040(VerifyAgent): - pass - - -class VerifyAgent1041(VerifyAgent439): - pass - - -class VerificationItem520(VerificationItem): - pass - - -class DeclaredBy521(DeclaredBy): - pass - - -class VerifyAgent1042(VerifyAgent): - pass - - -class VerifyAgent1043(VerifyAgent439): - pass - - -class VerificationItem521(VerificationItem): - pass - - -class ReferenceAsset19(ReferenceAsset): - pass - - -class FeedFieldMapping20(FeedFieldMapping): - pass - - -class ReferenceAuthorization18(ReferenceAuthorization): - pass - - -class DeclaredBy522(DeclaredBy): - pass - - -class VerifyAgent1044(VerifyAgent): - pass - - -class VerifyAgent1045(VerifyAgent439): - pass - - -class VerificationItem522(VerificationItem): - pass - - -class DeclaredBy523(DeclaredBy): - pass - - -class VerifyAgent1046(VerifyAgent): - pass - - -class VerifyAgent1047(VerifyAgent439): - pass - - -class VerificationItem523(VerificationItem): - pass - - -class DeclaredBy524(DeclaredBy): - pass - - -class VerifyAgent1048(VerifyAgent): - pass - - -class VerifyAgent1049(VerifyAgent439): - pass - - -class VerificationItem524(VerificationItem): - pass - - -class DeclaredBy525(DeclaredBy): - pass - - -class VerifyAgent1050(VerifyAgent): - pass - - -class VerifyAgent1051(VerifyAgent439): - pass - - -class VerificationItem525(VerificationItem): - pass - - -class DeclaredBy526(DeclaredBy): - pass - - -class VerifyAgent1052(VerifyAgent): - pass - - -class VerifyAgent1053(VerifyAgent439): - pass - - -class VerificationItem526(VerificationItem): - pass - - -class DeclaredBy527(DeclaredBy): - pass - - -class VerifyAgent1054(VerifyAgent): - pass - - -class VerifyAgent1055(VerifyAgent439): - pass - - -class VerificationItem527(VerificationItem): - pass - - -class DeclaredBy528(DeclaredBy): - pass - - -class VerifyAgent1056(VerifyAgent): - pass - - -class VerifyAgent1057(VerifyAgent439): - pass - - -class VerificationItem528(VerificationItem): - pass - - -class DeclaredBy529(DeclaredBy): - pass - - -class VerifyAgent1058(VerifyAgent): - pass - - -class VerifyAgent1059(VerifyAgent439): - pass - - -class VerificationItem529(VerificationItem): - pass - - -class DeclaredBy530(DeclaredBy): - pass - - -class VerifyAgent1060(VerifyAgent): - pass - - -class VerifyAgent1061(VerifyAgent439): - pass - - -class VerificationItem530(VerificationItem): - pass - - -class DeclaredBy531(DeclaredBy): - pass - - -class VerifyAgent1062(VerifyAgent): - pass - - -class VerifyAgent1063(VerifyAgent439): - pass - - -class VerificationItem531(VerificationItem): - pass - - -class DeclaredBy532(DeclaredBy): - pass - - -class VerifyAgent1064(VerifyAgent): - pass - - -class VerifyAgent1065(VerifyAgent439): - pass - - -class VerificationItem532(VerificationItem): - pass - - -class DeclaredBy533(DeclaredBy): - pass - - -class VerifyAgent1066(VerifyAgent): - pass - - -class VerifyAgent1067(VerifyAgent439): - pass - - -class VerificationItem533(VerificationItem): - pass - - -class DeclaredBy534(DeclaredBy): - pass - - -class VerifyAgent1068(VerifyAgent): - pass - - -class VerifyAgent1069(VerifyAgent439): - pass - - -class VerificationItem534(VerificationItem): - pass - - -class DeclaredBy535(DeclaredBy): - pass - - -class VerifyAgent1070(VerifyAgent): - pass - - -class VerifyAgent1071(VerifyAgent439): - pass - - -class VerificationItem535(VerificationItem): - pass - - -class DeclaredBy536(DeclaredBy): - pass - - -class VerifyAgent1072(VerifyAgent): - pass - - -class VerifyAgent1073(VerifyAgent439): - pass - - -class VerificationItem536(VerificationItem): - pass - - -class DeclaredBy537(DeclaredBy): - pass - - -class VerifyAgent1074(VerifyAgent): - pass - - -class VerifyAgent1075(VerifyAgent439): - pass - - -class VerificationItem537(VerificationItem): - pass - - -class DeclaredBy538(DeclaredBy): - pass - - -class VerifyAgent1076(VerifyAgent): - pass - - -class VerifyAgent1077(VerifyAgent439): - pass - - -class VerificationItem538(VerificationItem): - pass - - -class DeclaredBy539(DeclaredBy): - pass - - -class VerifyAgent1078(VerifyAgent): - pass - - -class VerifyAgent1079(VerifyAgent439): - pass - - -class VerificationItem539(VerificationItem): - pass - - -class DeclaredBy540(DeclaredBy): - pass - - -class VerifyAgent1080(VerifyAgent): - pass - - -class VerifyAgent1081(VerifyAgent439): - pass - - -class VerificationItem540(VerificationItem): - pass - - -class DeclaredBy541(DeclaredBy): - pass - - -class VerifyAgent1082(VerifyAgent): - pass - - -class VerifyAgent1083(VerifyAgent439): - pass - - -class VerificationItem541(VerificationItem): - pass - - -class DeclaredBy542(DeclaredBy): - pass - - -class VerifyAgent1084(VerifyAgent): - pass - - -class VerifyAgent1085(VerifyAgent439): - pass - - -class VerificationItem542(VerificationItem): - pass - - -class DeclaredBy543(DeclaredBy): - pass - - -class VerifyAgent1086(VerifyAgent): - pass - - -class VerifyAgent1087(VerifyAgent439): - pass - - -class VerificationItem543(VerificationItem): - pass - - -class ReferenceAsset20(ReferenceAsset): - pass - - -class FeedFieldMapping21(FeedFieldMapping): - pass - - -class ReferenceAuthorization19(ReferenceAuthorization): - pass - - -class DeclaredBy544(DeclaredBy): - pass - - -class VerifyAgent1088(VerifyAgent): - pass - - -class VerifyAgent1089(VerifyAgent439): - pass - - -class VerificationItem544(VerificationItem): - pass - - -class DeclaredBy545(DeclaredBy): - pass - - -class VerifyAgent1090(VerifyAgent): - pass - - -class VerifyAgent1091(VerifyAgent439): - pass - - -class VerificationItem545(VerificationItem): - pass - - -class DeclaredBy546(DeclaredBy): - pass - - -class VerifyAgent1092(VerifyAgent): - pass - - -class VerifyAgent1093(VerifyAgent439): - pass - - -class VerificationItem546(VerificationItem): - pass - - -class DeclaredBy547(DeclaredBy): - pass - - -class VerifyAgent1094(VerifyAgent): - pass - - -class VerifyAgent1095(VerifyAgent439): - pass - - -class VerificationItem547(VerificationItem): - pass - - -class DeclaredBy548(DeclaredBy): - pass - - -class VerifyAgent1096(VerifyAgent): - pass - - -class VerifyAgent1097(VerifyAgent439): - pass - - -class VerificationItem548(VerificationItem): - pass - - -class DeclaredBy549(DeclaredBy): - pass - - -class VerifyAgent1098(VerifyAgent): - pass - - -class VerifyAgent1099(VerifyAgent439): - pass - - -class VerificationItem549(VerificationItem): - pass - - -class DeclaredBy550(DeclaredBy): - pass - - -class VerifyAgent1100(VerifyAgent): - pass - - -class VerifyAgent1101(VerifyAgent439): - pass - - -class VerificationItem550(VerificationItem): - pass - - -class DeclaredBy551(DeclaredBy): - pass - - -class VerifyAgent1102(VerifyAgent): - pass - - -class VerifyAgent1103(VerifyAgent439): - pass - - -class VerificationItem551(VerificationItem): - pass - - -class DeclaredBy552(DeclaredBy): - pass - - -class VerifyAgent1104(VerifyAgent): - pass - - -class VerifyAgent1105(VerifyAgent439): - pass - - -class VerificationItem552(VerificationItem): - pass - - -class DeclaredBy553(DeclaredBy): - pass - - -class VerifyAgent1106(VerifyAgent): - pass - - -class VerifyAgent1107(VerifyAgent439): - pass - - -class VerificationItem553(VerificationItem): - pass - - -class Feature(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - feature_id: Annotated[ - str, - Field( - description="The feature that was evaluated (e.g., 'auto_redirect', 'brand_consistency'). Features prefixed with 'registry:' reference standardized policies from the shared policy registry (e.g., 'registry:eu_ai_act_article_50'). Unprefixed feature IDs are agent-defined." - ), - ] - value: Annotated[ - bool | float | str, - Field( - description='The feature value. Type depends on feature definition: boolean for binary, number for quantitative, string for categorical.' - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of measurement for quantitative values (e.g., 'percentage', 'score')" - ), - ] = None - confidence: Annotated[ - float | None, Field(description='Confidence score for this value (0-1)', ge=0.0, le=1.0) - ] = None - measured_at: Annotated[ - AwareDatetime | None, Field(description='When this feature was evaluated') - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field(description='When this evaluation expires and should be refreshed'), - ] = None - methodology_version: Annotated[ - str | None, Field(description='Version of the methodology used to evaluate this feature') - ] = None - details: Annotated[ - dict[str, Any] | None, - Field(description='Additional vendor-specific details about this evaluation'), - ] = None - policy_id: Annotated[ - str | None, - Field( - description='Optional attribution — when this feature was evaluated for the purpose of a specific policy, policy_id references the authorizing PolicyEntry. Creative agents and sellers populate when the measurement was motivated by a specific policy; do NOT populate when the feature is a generic measurement (carbon score, brand consistency) unrelated to any policy. See /docs/governance/policy-attribution.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Eval(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - features: Annotated[ - list[Feature] | None, - Field( - description='Creative-feature values the evaluator measured for this leaf — `creative-feature-result[]` (e.g., a `creative_quality_score` number, an `on_brief` categorical, a calibrated `predicted_performance` in [0,1] from the exemplars form). The gate (evaluator.feature_requirement[]) and ranking (evaluator.rank_by) are evaluated over these values. Same shape get_creative_features returns.' - ), - ] = None - ranked_against: Annotated[ - int | None, - Field( - description='Number of leaves this leaf was scored against (the best-of-N N). Lets the buyer interpret rank in context.', - ge=1, - ), - ] = None - calls_used: Annotated[ - int | None, - Field( - description='Number of judge calls made during evaluation. Sellers SHOULD populate when agent_url was used and an eval_budget was supplied, giving buyers visibility into external call usage. No billing coupling in v1.', - ge=0, - ), - ] = None - seconds_used: Annotated[ - float | None, - Field( - description='Wall-clock seconds consumed during evaluation. Sellers SHOULD populate when agent_url was used and an eval_budget was supplied, giving buyers visibility into external call usage. No billing coupling in v1.', - ge=0.0, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue46(Issue): - pass - - -class Error18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue46] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class KeepModeApplied(StrEnum): - keep_all = 'keep_all' - keep_one = 'keep_one' - keep_some = 'keep_some' - - -class SelectionStrategyApplied(StrEnum): - audience_relevance = 'audience_relevance' - contextual_fit = 'contextual_fit' - performance = 'performance' - proximity = 'proximity' - inventory_priority = 'inventory_priority' - random = 'random' - - -class BudgetStatus(StrEnum): - complete = 'complete' - capped = 'capped' - - -class Issue47(Issue): - pass - - -class Error19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue47] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue48(Issue): - pass - - -class AdcpError24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue48] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Basis(StrEnum): - fixed = 'fixed' - estimated_units = 'estimated_units' - cpm_deferred = 'cpm_deferred' - - -class ConsumptionEstimate(Consumption): - pass - - -class PerLeafItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_item_ref: dict[str, Any] | None = None - variant_axis_value: Any | None = None - pricing_option_id: str | None = None - cost_low: Annotated[float | None, Field(ge=0.0)] = None - cost_high: Annotated[float | None, Field(ge=0.0)] = None - consumption_estimate: Annotated[ - ConsumptionEstimate | None, - Field( - description='Structured consumption details returned by build_creative when a paid creative agent computes cost. Contains well-known fields for common consumption metrics. The consumption object is informational — it lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth, not consumption.', - title='Creative Consumption', - ), - ] = None - - -class Estimate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - items_total: Annotated[ - int | None, - Field(description='Catalog items eligible (before max_creatives sampling).', ge=0), - ] = None - items_to_produce: Annotated[ - int | None, - Field(description='Distinct creatives that would be produced (after max_creatives).', ge=0), - ] = None - conditions_total: Annotated[ - int | None, - Field( - description='Signal-fan-out conditions axis count (len(signal_conditions)). Present when signal_conditions was sent so leaves_total = items_to_produce × conditions_total × variants_per_item is legible before spend.', - ge=1, - ), - ] = None - variants_per_item: Annotated[ - int | None, Field(description='Alternatives per creative (max_variants).', ge=1) - ] = None - leaves_total: Annotated[ - int | None, - Field( - description='Total billable leaves = items_to_produce × variants_per_item (× conditions_total when signal_conditions was sent — see conditions_total).', - ge=0, - ), - ] = None - currency: Annotated[ - str, Field(description='ISO 4217 currency for the cost band.', pattern='^[A-Z]{3}$') - ] - cost_low: Annotated[ - float, Field(description='Low end of the projected aggregate vendor_cost.', ge=0.0) - ] - cost_high: Annotated[ - float, - Field(description='High end. For basis "fixed" this equals cost_low (exact).', ge=0.0), - ] - cost_expected: Annotated[ - float | None, Field(description='Optional point estimate within the band.', ge=0.0) - ] = None - basis: Annotated[ - Basis, - Field( - description='`fixed` = per-format flat pricing (cost_low == cost_high, exact). `estimated_units` = generative per_unit where the seller projects a unit range (band reflects the uncertainty in seconds/images/tokens). `cpm_deferred` = CPM-priced; build-time cost is 0 and cost accrues at serve time (mirrors the vendor_cost:0 CPM convention).' - ), - ] - per_leaf: Annotated[ - list[PerLeafItem] | None, Field(description='Optional per-leaf breakdown of the estimate.') - ] = None - - -class Issue49(Issue): - pass - - -class AdcpError25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue49] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue50(Issue): - pass - - -class Error20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue50] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue51(Issue): - pass - - -class AdcpError26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue51] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue52(Issue): - pass - - -class Error21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue52] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result580(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'generating_assets', 'resolving_macros', 'rendering_preview')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason16(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - CREATIVE_DIRECTION_NEEDED = 'CREATIVE_DIRECTION_NEEDED' - ASSET_SELECTION_NEEDED = 'ASSET_SELECTION_NEEDED' - - -class Issue53(Issue): - pass - - -class Error22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue53] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result581(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason16 | None, Field(description='Reason code indicating why input is needed') - ] = None - errors: Annotated[ - list[Error22] | None, - Field( - description='Optional validation errors or warnings explaining why input is required.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue54(Issue): - pass - - -class Error23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue54] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result582(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shapes, whose creative_manifest or creative_manifests are issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Generative build queued; typical turnaround 3–5 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error23] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue55(Issue): - pass - - -class AdcpError27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue55] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class DeclaredBy554(DeclaredBy): - pass - - -class VerifyAgent1108(VerifyAgent): - pass - - -class VerifyAgent1109(VerifyAgent439): - pass - - -class VerificationItem554(VerificationItem): - pass - - -class Contact12(Contact): - pass - - -class BillingEntity1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - legal_name: Annotated[ - str, Field(description='Registered legal name of the business entity', max_length=200) - ] - vat_id: Annotated[ - str | None, - Field( - description='VAT identification number (e.g., DE123456789 for Germany, FR12345678901 for France). Required for B2B invoicing in the EU. Must be normalized: no spaces, dots, or dashes.', - pattern='^[A-Z]{2}[A-Z0-9]{2,13}$', - ), - ] = None - tax_id: Annotated[ - str | None, - Field( - description='Tax identification number for jurisdictions that do not use VAT (e.g., US EIN)', - max_length=30, - ), - ] = None - registration_number: Annotated[ - str | None, - Field( - description='Company registration number (e.g., HRB 12345 for German Handelsregister)', - max_length=50, - ), - ] = None - address: Annotated[ - Address | None, Field(description='Postal address for invoicing and legal correspondence') - ] = None - contacts: Annotated[ - list[Contact12] | None, - Field( - description='Contacts for billing, legal, and operational matters. Contains personal data subject to GDPR and equivalent regulations. Implementations MUST use this data only for invoicing and account management.', - max_length=10, - ), - ] = None - bank: Annotated[ - Bank | None, - Field( - description='Bank account details for payment processing. Write-only: included in requests to provide payment coordinates, but MUST NOT be echoed in responses. Sellers store these details and confirm receipt without returning them.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Action(StrEnum): - created = 'created' - updated = 'updated' - unchanged = 'unchanged' - failed = 'failed' - deleted = 'deleted' - - -class Status154(StrEnum): - processing = 'processing' - pending_review = 'pending_review' - approved = 'approved' - suspended = 'suspended' - rejected = 'rejected' - archived = 'archived' - - -class Issue56(Issue): - pass - - -class Error24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue56] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue57(Issue): - pass - - -class AdcpError28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue57] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue58(Issue): - pass - - -class Error25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue58] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue59(Issue): - pass - - -class AdcpError29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue59] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue60(Issue): - pass - - -class Error26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue60] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result587(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, Field(description='Current step or phase of the operation') - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - creatives_processed: Annotated[ - int | None, Field(description='Number of creatives processed so far', ge=0) - ] = None - creatives_total: Annotated[ - int | None, Field(description='Total number of creatives to process', ge=0) - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason17(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - ASSET_CONFIRMATION = 'ASSET_CONFIRMATION' - FORMAT_CLARIFICATION = 'FORMAT_CLARIFICATION' - - -class Result588(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason17 | None, Field(description='Reason code indicating why buyer input is needed') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue61(Issue): - pass - - -class Error27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue61] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result589(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose creatives array carries per-item approval state via CreativeStatus. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. The creatives array is issued on the completion artifact, not here. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Batch ingestion queued; typical turnaround 15-30 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error27] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue62(Issue): - pass - - -class AdcpError30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue62] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Status155(StrEnum): - approved = 'approved' - pending = 'pending' - rejected = 'rejected' - warning = 'warning' - withdrawn = 'withdrawn' - - -class ItemIssue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - item_id: Annotated[str, Field(description='ID of the catalog item with an issue')] - status: Annotated[ - Status155, Field(description='Item review status', title='Catalog Item Status') - ] - reasons: Annotated[list[str] | None, Field(description='Reasons for rejection or warning')] = ( - None - ) - - -class Issue63(Issue): - pass - - -class Error28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue63] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Catalog(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[str, Field(description='Catalog ID from the request')] - action: Annotated[ - Action, Field(description='Action taken for this catalog', title='Catalog Action') - ] - platform_id: Annotated[ - str | None, Field(description='Platform-specific ID assigned to the catalog') - ] = None - item_count: Annotated[ - int | None, - Field( - description="Total number of items in the catalog after sync. Required when action is 'created', 'updated', or 'unchanged'. Omitted on 'failed' and 'deleted'.", - ge=0, - ), - ] = None - items_approved: Annotated[ - int | None, - Field( - description='Number of items approved by the platform. Populated when the platform performs item-level review.', - ge=0, - ), - ] = None - items_pending: Annotated[ - int | None, - Field( - description='Number of items pending platform review. Common for product catalogs where items must pass content policy checks.', - ge=0, - ), - ] = None - items_rejected: Annotated[ - int | None, - Field( - description='Number of items rejected by the platform. Check item_issues for rejection reasons.', - ge=0, - ), - ] = None - item_issues: Annotated[ - list[ItemIssue] | None, - Field( - description='Per-item issues reported by the platform (rejections, warnings). Only present when the platform performs item-level review.' - ), - ] = None - last_synced_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when the most recent sync was accepted by the platform' - ), - ] = None - next_fetch_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp of when the platform will next fetch the feed URL. Only present for URL-based catalogs with update_frequency.' - ), - ] = None - changes: Annotated[ - list[str] | None, - Field(description="Field names that were modified (only present when action='updated')"), - ] = None - errors: Annotated[ - list[Error28] | None, - Field(description="Validation or processing errors (only present when action='failed')"), - ] = None - warnings: Annotated[ - list[str] | None, Field(description='Non-fatal warnings about this catalog') - ] = None - - -class Issue64(Issue): - pass - - -class AdcpError31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue64] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue65(Issue): - pass - - -class Error29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue65] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue66(Issue): - pass - - -class AdcpError32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue66] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Issue67(Issue): - pass - - -class Error30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue67] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result593(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - percentage: Annotated[ - float | None, Field(description='Completion percentage (0-100)', ge=0.0, le=100.0) - ] = None - current_step: Annotated[ - str | None, - Field( - description="Current step or phase of the operation (e.g., 'Fetching product feed', 'Validating items', 'Platform review')" - ), - ] = None - total_steps: Annotated[ - int | None, Field(description='Total number of steps in the operation', ge=1) - ] = None - step_number: Annotated[int | None, Field(description='Current step number', ge=1)] = None - catalogs_processed: Annotated[ - int | None, Field(description='Number of catalogs processed so far', ge=0) - ] = None - catalogs_total: Annotated[ - int | None, Field(description='Total number of catalogs to process', ge=0) - ] = None - items_processed: Annotated[ - int | None, - Field(description='Total number of catalog items processed across all catalogs', ge=0), - ] = None - items_total: Annotated[ - int | None, - Field(description='Total number of catalog items to process across all catalogs', ge=0), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Reason18(StrEnum): - APPROVAL_REQUIRED = 'APPROVAL_REQUIRED' - FEED_VALIDATION = 'FEED_VALIDATION' - ITEM_REVIEW = 'ITEM_REVIEW' - FEED_ACCESS = 'FEED_ACCESS' - - -class Result594(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason18 | None, - Field( - description='Reason code indicating why buyer input is needed. APPROVAL_REQUIRED: platform requires explicit approval before activating the catalog. FEED_VALIDATION: feed URL returned unexpected format or schema errors. ITEM_REVIEW: platform flagged items for manual review. FEED_ACCESS: platform cannot access the feed URL (authentication, CORS, etc.).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue68(Issue): - pass - - -class Error31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue68] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Result595(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Literal['submitted'], - Field( - description='Task-level status literal. Discriminates this async envelope from the synchronous success shape, whose catalogs array is issued in-line. See task-status.json for the full task-status enum.' - ), - ] = 'submitted' - task_id: Annotated[ - str, - Field( - description='Task handle the buyer uses with tasks/get, and that the seller references on push-notification callbacks. Per AdCP wire conventions this is snake_case; A2A adapters MAY surface it as taskId, but the payload field emitted by the agent is task_id.' - ), - ] - message: Annotated[ - str | None, - Field( - description="Optional human-readable explanation of why the task is submitted — e.g., 'Catalog ingestion queued; typical turnaround 5–15 minutes.' Plain text only. Buyers MUST treat this as untrusted seller input: escape before rendering to HTML UIs, and sanitize or isolate before passing to an LLM prompt context — a hostile seller may inject prompt-injection payloads aimed at the buyer's agent.", - max_length=2000, - ), - ] = None - errors: Annotated[ - list[Error31] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class AuthenticationScheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class AdCPProtocol(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - governance = 'governance' - creative = 'creative' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - measurement = 'measurement' - - -class MediaChannel(StrEnum): - display = 'display' - olv = 'olv' - social = 'social' - search = 'search' - ctv = 'ctv' - linear_tv = 'linear_tv' - radio = 'radio' - streaming_audio = 'streaming_audio' - podcast = 'podcast' - dooh = 'dooh' - ooh = 'ooh' - print = 'print' - cinema = 'cinema' - email = 'email' - gaming = 'gaming' - retail_media = 'retail_media' - influencer = 'influencer' - affiliate = 'affiliate' - product_placement = 'product_placement' - sponsored_intelligence = 'sponsored_intelligence' - - -class LogoSlot(StrEnum): - logo_card_light = 'logo_card_light' - logo_card_dark = 'logo_card_dark' - profile_mark = 'profile_mark' - favicon = 'favicon' - app_icon = 'app_icon' - social_profile_mark = 'social_profile_mark' - nav_header = 'nav_header' - footer = 'footer' - email_header = 'email_header' - watermark = 'watermark' - ad_end_card = 'ad_end_card' - co_brand_lockup = 'co_brand_lockup' - marketplace_listing = 'marketplace_listing' - - -class VideoPlacementType(StrEnum): - instream = 'instream' - accompanying_content = 'accompanying_content' - interstitial = 'interstitial' - standalone = 'standalone' - - -class AudioDistributionType(StrEnum): - music_streaming_service = 'music_streaming_service' - fm_am_broadcast = 'fm_am_broadcast' - podcast = 'podcast' - catch_up_radio = 'catch_up_radio' - web_radio = 'web_radio' - video_game = 'video_game' - text_to_speech = 'text_to_speech' - - -class SponsoredPlacementType(StrEnum): - sponsored_search = 'sponsored_search' - sponsored_display = 'sponsored_display' - sponsored_native = 'sponsored_native' - - -class SocialPlacementSurface(StrEnum): - feed = 'feed' - stories = 'stories' - short_video = 'short_video' - explore = 'explore' - search = 'search' - - -class DeliveryType(StrEnum): - guaranteed = 'guaranteed' - non_guaranteed = 'non_guaranteed' - - -class Exclusivity(StrEnum): - none = 'none' - category = 'category' - exclusive = 'exclusive' - - -class PriceAdjustmentKind(StrEnum): - fee = 'fee' - discount = 'discount' - commission = 'commission' - settlement = 'settlement' - - -class DemographicSystem(StrEnum): - nielsen = 'nielsen' - barb = 'barb' - agf = 'agf' - oztam = 'oztam' - mediametrie = 'mediametrie' - custom = 'custom' - - -class EventType(StrEnum): - page_view = 'page_view' - view_content = 'view_content' - select_content = 'select_content' - select_item = 'select_item' - search = 'search' - share = 'share' - add_to_cart = 'add_to_cart' - remove_from_cart = 'remove_from_cart' - viewed_cart = 'viewed_cart' - add_to_wishlist = 'add_to_wishlist' - initiate_checkout = 'initiate_checkout' - add_payment_info = 'add_payment_info' - purchase = 'purchase' - refund = 'refund' - lead = 'lead' - qualify_lead = 'qualify_lead' - close_convert_lead = 'close_convert_lead' - disqualify_lead = 'disqualify_lead' - complete_registration = 'complete_registration' - subscribe = 'subscribe' - follow = 'follow' - content_view = 'content_view' - watch_milestone = 'watch_milestone' - start_trial = 'start_trial' - app_install = 'app_install' - app_launch = 'app_launch' - contact = 'contact' - schedule = 'schedule' - donate = 'donate' - submit_application = 'submit_application' - custom = 'custom' - - -class GeographicTargetingLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class MetroAreaSystem(StrEnum): - nielsen_dma = 'nielsen_dma' - uk_itl1 = 'uk_itl1' - uk_itl2 = 'uk_itl2' - eurostat_nuts2 = 'eurostat_nuts2' - custom = 'custom' - - -class LegacyPostalCodeSystem(StrEnum): - us_zip = 'us_zip' - us_zip_plus_four = 'us_zip_plus_four' - gb_outward = 'gb_outward' - gb_full = 'gb_full' - ca_fsa = 'ca_fsa' - ca_full = 'ca_full' - de_plz = 'de_plz' - fr_code_postal = 'fr_code_postal' - au_postcode = 'au_postcode' - ch_plz = 'ch_plz' - at_plz = 'at_plz' - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class ViewabilityStandard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class ForecastRangeUnit(StrEnum): - spend = 'spend' - availability = 'availability' - reach_freq = 'reach_freq' - weekly = 'weekly' - daily = 'daily' - clicks = 'clicks' - conversions = 'conversions' - package = 'package' - - -class ForecastMethod(StrEnum): - estimate = 'estimate' - modeled = 'modeled' - guaranteed = 'guaranteed' - - -class ReachUnit(StrEnum): - individuals = 'individuals' - households = 'households' - devices = 'devices' - accounts = 'accounts' - cookies = 'cookies' - custom = 'custom' - - -class MakegoodRemedy(StrEnum): - additional_delivery = 'additional_delivery' - credit = 'credit' - invoice_adjustment = 'invoice_adjustment' - - -class PerformanceStandardMetric(StrEnum): - viewability = 'viewability' - ivt = 'ivt' - completion_rate = 'completion_rate' - brand_safety = 'brand_safety' - attention_score = 'attention_score' - - -class MediaBuyValidAction(StrEnum): - pause = 'pause' - resume = 'resume' - cancel = 'cancel' - extend_flight = 'extend_flight' - shorten_flight = 'shorten_flight' - update_flight_dates = 'update_flight_dates' - increase_budget = 'increase_budget' - decrease_budget = 'decrease_budget' - reallocate_budget = 'reallocate_budget' - update_targeting = 'update_targeting' - update_pacing = 'update_pacing' - update_frequency_caps = 'update_frequency_caps' - replace_creative = 'replace_creative' - update_creative_assignments = 'update_creative_assignments' - remove_creative = 'remove_creative' - add_packages = 'add_packages' - remove_packages = 'remove_packages' - update_budget = 'update_budget' - update_dates = 'update_dates' - update_packages = 'update_packages' - sync_creatives = 'sync_creatives' - - -class MediaBuyActionMode(StrEnum): - self_serve = 'self_serve' - conditional_self_serve = 'conditional_self_serve' - requires_approval = 'requires_approval' - - -class MediaBuyStatus(StrEnum): - pending_creatives = 'pending_creatives' - pending_start = 'pending_start' - active = 'active' - paused = 'paused' - completed = 'completed' - rejected = 'rejected' - canceled = 'canceled' - - -class ReportingFrequency(StrEnum): - hourly = 'hourly' - daily = 'daily' - monthly = 'monthly' - - -class AvailableMetric(StrEnum): - impressions = 'impressions' - spend = 'spend' - clicks = 'clicks' - ctr = 'ctr' - views = 'views' - completed_views = 'completed_views' - completion_rate = 'completion_rate' - conversions = 'conversions' - conversion_value = 'conversion_value' - roas = 'roas' - cost_per_acquisition = 'cost_per_acquisition' - new_to_brand_rate = 'new_to_brand_rate' - leads = 'leads' - reach = 'reach' - frequency = 'frequency' - grps = 'grps' - engagements = 'engagements' - engagement_rate = 'engagement_rate' - follows = 'follows' - saves = 'saves' - profile_visits = 'profile_visits' - viewability = 'viewability' - quartile_data = 'quartile_data' - dooh_metrics = 'dooh_metrics' - cost_per_click = 'cost_per_click' - cost_per_completed_view = 'cost_per_completed_view' - cpm = 'cpm' - downloads = 'downloads' - units_sold = 'units_sold' - new_to_brand_units = 'new_to_brand_units' - plays = 'plays' - incremental_sales_lift = 'incremental_sales_lift' - brand_lift = 'brand_lift' - foot_traffic = 'foot_traffic' - conversion_lift = 'conversion_lift' - brand_search_lift = 'brand_search_lift' - - -class CoBrandingRequirement(StrEnum): - required = 'required' - optional = 'optional' - none = 'none' - - -class LandingPageRequirement(StrEnum): - any = 'any' - retailer_site_only = 'retailer_site_only' - must_include_retailer = 'must_include_retailer' - - -class SignalValueType(StrEnum): - binary = 'binary' - categorical = 'categorical' - numeric = 'numeric' - - -class CatalogType(StrEnum): - offering = 'offering' - product = 'product' - inventory = 'inventory' - store = 'store' - promotion = 'promotion' - hotel = 'hotel' - flight = 'flight' - job = 'job' - vehicle = 'vehicle' - real_estate = 'real_estate' - education = 'education' - destination = 'destination' - app = 'app' - - -class AssessmentStatus(StrEnum): - insufficient = 'insufficient' - minimum = 'minimum' - good = 'good' - excellent = 'excellent' - - -class ActionSource(StrEnum): - website = 'website' - app = 'app' - offline = 'offline' - phone_call = 'phone_call' - chat = 'chat' - email = 'email' - in_store = 'in_store' - system_generated = 'system_generated' - other = 'other' - - -class InstallmentStatus(StrEnum): - scheduled = 'scheduled' - tentative = 'tentative' - live = 'live' - postponed = 'postponed' - cancelled = 'cancelled' - aired = 'aired' - published = 'published' - - -class ContentRatingSystem(StrEnum): - tv_parental = 'tv_parental' - mpaa = 'mpaa' - podcast = 'podcast' - esrb = 'esrb' - bbfc = 'bbfc' - fsk = 'fsk' - acb = 'acb' - chvrs = 'chvrs' - csa = 'csa' - pegi = 'pegi' - custom = 'custom' - - -class SpecialCategory(StrEnum): - awards = 'awards' - championship = 'championship' - concert = 'concert' - conference = 'conference' - election = 'election' - festival = 'festival' - gala = 'gala' - holiday = 'holiday' - premiere = 'premiere' - product_launch = 'product_launch' - reunion = 'reunion' - tribute = 'tribute' - - -class TalentRole(StrEnum): - host = 'host' - guest = 'guest' - creator = 'creator' - cast = 'cast' - narrator = 'narrator' - producer = 'producer' - correspondent = 'correspondent' - commentator = 'commentator' - analyst = 'analyst' - - -class DerivativeType(StrEnum): - clip = 'clip' - highlight = 'highlight' - recap = 'recap' - trailer = 'trailer' - bonus = 'bonus' - - -class TMPResponseType(StrEnum): - activation = 'activation' - catalog_items = 'catalog_items' - creative = 'creative' - deal = 'deal' - - -class UIDType(StrEnum): - rampid = 'rampid' - rampid_derived = 'rampid_derived' - id5 = 'id5' - uid2 = 'uid2' - euid = 'euid' - pairid = 'pairid' - maid = 'maid' - hashed_email = 'hashed_email' - publisher_first_party = 'publisher_first_party' - world_id_nullifier = 'world_id_nullifier' - other = 'other' - - -class DayOfWeek(StrEnum): - monday = 'monday' - tuesday = 'tuesday' - wednesday = 'wednesday' - thursday = 'thursday' - friday = 'friday' - saturday = 'saturday' - sunday = 'sunday' - - -class AccountStatus(StrEnum): - active = 'active' - pending_approval = 'pending_approval' - rejected = 'rejected' - payment_required = 'payment_required' - suspended = 'suspended' - closed = 'closed' - - -class BillingParty(StrEnum): - operator = 'operator' - agent = 'agent' - advertiser = 'advertiser' - - -class PaymentTerms(StrEnum): - net_15 = 'net_15' - net_30 = 'net_30' - net_45 = 'net_45' - net_60 = 'net_60' - net_90 = 'net_90' - prepay = 'prepay' - - -class AccountScope(StrEnum): - operator = 'operator' - brand = 'brand' - operator_brand = 'operator_brand' - agent = 'agent' - - -class CloudStorageProtocol(StrEnum): - s3 = 's3' - gcs = 'gcs' - azure_blob = 'azure_blob' - - -class NotificationType(StrEnum): - scheduled = 'scheduled' - final = 'final' - delayed = 'delayed' - adjusted = 'adjusted' - impairment = 'impairment' - creative_status_changed = 'creative.status_changed' - creative_purged = 'creative.purged' - product_created = 'product.created' - product_updated = 'product.updated' - product_priced = 'product.priced' - product_removed = 'product.removed' - signal_created = 'signal.created' - signal_updated = 'signal.updated' - signal_priced = 'signal.priced' - signal_removed = 'signal.removed' - wholesale_feed_bulk_change = 'wholesale_feed.bulk_change' - - -class Pacing(StrEnum): - even = 'even' - asap = 'asap' - front_loaded = 'front_loaded' - - -class FeedFormat(StrEnum): - google_merchant_center = 'google_merchant_center' - facebook_catalog = 'facebook_catalog' - shopify = 'shopify' - linkedin_jobs = 'linkedin_jobs' - tiktok_shop = 'tiktok_shop' - pinterest_catalog = 'pinterest_catalog' - openai_product_feed = 'openai_product_feed' - custom = 'custom' - - -class UpdateFrequency(StrEnum): - realtime = 'realtime' - hourly = 'hourly' - daily = 'daily' - weekly = 'weekly' - - -class ContentIDType(StrEnum): - sku = 'sku' - gtin = 'gtin' - offering_id = 'offering_id' - job_id = 'job_id' - hotel_id = 'hotel_id' - flight_id = 'flight_id' - vehicle_id = 'vehicle_id' - listing_id = 'listing_id' - store_id = 'store_id' - program_id = 'program_id' - destination_id = 'destination_id' - app_id = 'app_id' - - -class CanonicalFormatKind(StrEnum): - image = 'image' - html5 = 'html5' - display_tag = 'display_tag' - image_carousel = 'image_carousel' - video_hosted = 'video_hosted' - video_vast = 'video_vast' - audio_hosted = 'audio_hosted' - audio_daast = 'audio_daast' - sponsored_placement = 'sponsored_placement' - native_in_feed = 'native_in_feed' - responsive_creative = 'responsive_creative' - agent_placement = 'agent_placement' - custom = 'custom' - - -class AgeVerificationMethod(StrEnum): - facial_age_estimation = 'facial_age_estimation' - id_document = 'id_document' - digital_id = 'digital_id' - credit_card = 'credit_card' - world_id = 'world_id' - - -class TravelTimeUnit(StrEnum): - min = 'min' - hr = 'hr' - - -class TransportMode(StrEnum): - walking = 'walking' - cycling = 'cycling' - driving = 'driving' - public_transport = 'public_transport' - - -class DistanceUnit(StrEnum): - km = 'km' - mi = 'mi' - m = 'm' - - -class MatchType(StrEnum): - broad = 'broad' - phrase = 'phrase' - exact = 'exact' - - -class CompletionSource(StrEnum): - seller_attested = 'seller_attested' - vendor_attested = 'vendor_attested' - - -class AttributionMethodology(StrEnum): - deterministic_purchase = 'deterministic_purchase' - probabilistic = 'probabilistic' - panel_based = 'panel_based' - modeled = 'modeled' - - -class LiftDimension(StrEnum): - awareness = 'awareness' - consideration = 'consideration' - favorability = 'favorability' - purchase_intent = 'purchase_intent' - ad_recall = 'ad_recall' - - -class AttributionModel(StrEnum): - last_touch = 'last_touch' - first_touch = 'first_touch' - linear = 'linear' - time_decay = 'time_decay' - data_driven = 'data_driven' - - -class CanceledBy(StrEnum): - buyer = 'buyer' - seller = 'seller' - - -class PricingModel(StrEnum): - cpm = 'cpm' - vcpm = 'vcpm' - cpc = 'cpc' - cpcv = 'cpcv' - cpv = 'cpv' - cpp = 'cpp' - cpa = 'cpa' - flat_rate = 'flat_rate' - time = 'time' - - -class FrameRateType(StrEnum): - constant = 'constant' - variable = 'variable' - - -class ScanType(StrEnum): - progressive = 'progressive' - interlaced = 'interlaced' - - -class GOPType(StrEnum): - closed = 'closed' - open = 'open' - - -class MoovAtomPosition(StrEnum): - start = 'start' - end = 'end' - - -class AudioChannelLayout(StrEnum): - mono = 'mono' - stereo = 'stereo' - field_5_1 = '5.1' - field_7_1 = '7.1' - - -class VASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - loaded = 'loaded' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - playerExpand = 'playerExpand' - playerCollapse = 'playerCollapse' - fullscreen = 'fullscreen' - exitFullscreen = 'exitFullscreen' - progress = 'progress' - acceptInvitation = 'acceptInvitation' - adExpand = 'adExpand' - adCollapse = 'adCollapse' - minimize = 'minimize' - overlayViewDuration = 'overlayViewDuration' - otherAdInteraction = 'otherAdInteraction' - interactiveStart = 'interactiveStart' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - closeLinear = 'closeLinear' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class URLAssetType(StrEnum): - clickthrough = 'clickthrough' - tracker_pixel = 'tracker_pixel' - tracker_script = 'tracker_script' - - -class JavaScriptModuleType(StrEnum): - esm = 'esm' - commonjs = 'commonjs' - script = 'script' - - -class HTTPMethod(StrEnum): - GET = 'GET' - POST = 'POST' - - -class UniversalMacro(StrEnum): - MEDIA_BUY_ID = 'MEDIA_BUY_ID' - PACKAGE_ID = 'PACKAGE_ID' - CREATIVE_ID = 'CREATIVE_ID' - CACHEBUSTER = 'CACHEBUSTER' - TIMESTAMP = 'TIMESTAMP' - CLICK_URL = 'CLICK_URL' - GDPR = 'GDPR' - GDPR_CONSENT = 'GDPR_CONSENT' - US_PRIVACY = 'US_PRIVACY' - GPP_STRING = 'GPP_STRING' - GPP_SID = 'GPP_SID' - IP_ADDRESS = 'IP_ADDRESS' - LIMIT_AD_TRACKING = 'LIMIT_AD_TRACKING' - DEVICE_TYPE = 'DEVICE_TYPE' - OS = 'OS' - OS_VERSION = 'OS_VERSION' - DEVICE_MAKE = 'DEVICE_MAKE' - DEVICE_MODEL = 'DEVICE_MODEL' - USER_AGENT = 'USER_AGENT' - APP_BUNDLE = 'APP_BUNDLE' - APP_NAME = 'APP_NAME' - COUNTRY = 'COUNTRY' - REGION = 'REGION' - CITY = 'CITY' - ZIP = 'ZIP' - DMA = 'DMA' - LAT = 'LAT' - LONG = 'LONG' - DEVICE_ID = 'DEVICE_ID' - DEVICE_ID_TYPE = 'DEVICE_ID_TYPE' - DOMAIN = 'DOMAIN' - PAGE_URL = 'PAGE_URL' - REFERRER = 'REFERRER' - KEYWORDS = 'KEYWORDS' - PLACEMENT_ID = 'PLACEMENT_ID' - FOLD_POSITION = 'FOLD_POSITION' - AD_WIDTH = 'AD_WIDTH' - AD_HEIGHT = 'AD_HEIGHT' - VIDEO_ID = 'VIDEO_ID' - VIDEO_TITLE = 'VIDEO_TITLE' - VIDEO_DURATION = 'VIDEO_DURATION' - VIDEO_CATEGORY = 'VIDEO_CATEGORY' - CONTENT_GENRE = 'CONTENT_GENRE' - CONTENT_RATING = 'CONTENT_RATING' - PLAYER_WIDTH = 'PLAYER_WIDTH' - PLAYER_HEIGHT = 'PLAYER_HEIGHT' - POD_POSITION = 'POD_POSITION' - POD_SIZE = 'POD_SIZE' - AD_BREAK_ID = 'AD_BREAK_ID' - STATION_ID = 'STATION_ID' - COLLECTION_NAME = 'COLLECTION_NAME' - INSTALLMENT_ID = 'INSTALLMENT_ID' - AUDIO_DURATION = 'AUDIO_DURATION' - TMPX = 'TMPX' - IMPRESSION_ID = 'IMPRESSION_ID' - AXEM = 'AXEM' - CATALOG_ID = 'CATALOG_ID' - SKU = 'SKU' - GTIN = 'GTIN' - OFFERING_ID = 'OFFERING_ID' - JOB_ID = 'JOB_ID' - HOTEL_ID = 'HOTEL_ID' - FLIGHT_ID = 'FLIGHT_ID' - VEHICLE_ID = 'VEHICLE_ID' - LISTING_ID = 'LISTING_ID' - STORE_ID = 'STORE_ID' - PROGRAM_ID = 'PROGRAM_ID' - DESTINATION_ID = 'DESTINATION_ID' - CREATIVE_VARIANT_ID = 'CREATIVE_VARIANT_ID' - APP_ITEM_ID = 'APP_ITEM_ID' - ITEM_NAME = 'ITEM_NAME' - ITEM_DESCRIPTION = 'ITEM_DESCRIPTION' - ITEM_TAGLINE = 'ITEM_TAGLINE' - ITEM_PRICE = 'ITEM_PRICE' - ITEM_PRICE_CURRENCY = 'ITEM_PRICE_CURRENCY' - - -class WebhookResponseType(StrEnum): - html = 'html' - json = 'json' - xml = 'xml' - javascript = 'javascript' - - -class WebhookSecurityMethod(StrEnum): - hmac_sha256 = 'hmac_sha256' - api_key = 'api_key' - none = 'none' - - -class DAASTTrackingEvent(StrEnum): - impression = 'impression' - creativeView = 'creativeView' - start = 'start' - firstQuartile = 'firstQuartile' - midpoint = 'midpoint' - thirdQuartile = 'thirdQuartile' - complete = 'complete' - mute = 'mute' - unmute = 'unmute' - pause = 'pause' - resume = 'resume' - rewind = 'rewind' - skip = 'skip' - progress = 'progress' - clickTracking = 'clickTracking' - customClick = 'customClick' - close = 'close' - error = 'error' - viewable = 'viewable' - notViewable = 'notViewable' - viewUndetermined = 'viewUndetermined' - measurableImpression = 'measurableImpression' - viewableImpression = 'viewableImpression' - - -class MarkdownFlavor(StrEnum): - commonmark = 'commonmark' - gfm = 'gfm' - - -class RightUse(StrEnum): - likeness = 'likeness' - voice = 'voice' - name = 'name' - endorsement = 'endorsement' - motion_capture = 'motion_capture' - signature = 'signature' - catchphrase = 'catchphrase' - sync = 'sync' - background_music = 'background_music' - editorial = 'editorial' - commercial = 'commercial' - ai_generated_image = 'ai_generated_image' - - -class RightType(StrEnum): - talent = 'talent' - character = 'character' - brand_ip = 'brand_ip' - music = 'music' - stock_media = 'stock_media' - - -class CreativeIdentifierType(StrEnum): - ad_id = 'ad_id' - isci = 'isci' - clearcast_clock = 'clearcast_clock' - idcrea = 'idcrea' - - -class DataProviderSignalSelector4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['all'], - Field( - description='Discriminator indicating all signals from this data provider are included' - ), - ] = 'all' - - -class SignalId56(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-zA-Z0-9_-]+$')] - - -class DataProviderSignalSelector5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_id'], - Field(description='Discriminator indicating selection by specific signal IDs'), - ] = 'by_id' - signal_ids: Annotated[ - list[SignalId56], - Field( - description="Specific signal IDs from the data provider's published signal definitions", - min_length=1, - ), - ] - - -class SignalTag(RootModel[str]): - root: Annotated[str, Field(pattern='^[a-z0-9_-]+$')] - - -class DataProviderSignalSelector6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - data_provider_domain: Annotated[ - str, - Field( - description="Domain where data provider's adagents.json is hosted (e.g., 'polk.com')", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - selection_type: Annotated[ - Literal['by_tag'], Field(description='Discriminator indicating selection by signal tags') - ] = 'by_tag' - signal_tags: Annotated[ - list[SignalTag], - Field( - description="Signal tags from the data provider's published signal definitions. Selector covers all signals with these tags", - min_length=1, - ), - ] - - -class DataProviderSignalSelector( - RootModel[ - DataProviderSignalSelector4 | DataProviderSignalSelector5 | DataProviderSignalSelector6 - ] -): - root: Annotated[ - DataProviderSignalSelector4 | DataProviderSignalSelector5 | DataProviderSignalSelector6, - Field( - description="Selects signals from a data provider's adagents.json signals[]. Used for product definitions and agent authorization. Supports three selection patterns: all signals, specific IDs, or by tags.", - discriminator='selection_type', - title='Data Provider Signal Selector', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[AuthenticationScheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Details(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: AdCPProtocol | None = None - operation: Annotated[str | None, Field(description='Specific operation that failed')] = None - specific_context: Annotated[ - dict[str, Any] | None, Field(description='Domain-specific error context') - ] = None - - -class Error5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[str, Field(description='Error code for programmatic handling')] - message: Annotated[str, Field(description='Detailed error message')] - details: Annotated[Details | None, Field(description='Additional error context')] = None - - -class PushNotificationConfig15(PushNotificationConfig): - pass - - -class Slot(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_group_id: Annotated[ - str, - Field( - description='Canonical asset_group_id from /schemas/core/asset-group-vocabulary.json. Non-canonical IDs are valid but trigger soft warnings.' - ), - ] - asset_type: Annotated[ - AssetType, - Field( - description="Discriminator selecting the asset schema this slot accepts. SDK codegen uses this to type the slot value. `published_post` is an existing-post reference asset, not uploaded media bytes and not a catalog row. `card` is the multi-card carousel element type (see card-asset.json). `pixel_tracker` / `vast_tracker` / `daast_tracker` are the renderer-fired measurement-tracker primitives — see `/schemas/core/assets/pixel-tracker-asset.json` and the VAST / DAAST tracker schemas. `object` is a last-resort fallback for structured non-asset inputs that don't fit any primitive asset_type — prefer specific types whenever possible." - ), - ] - required: Annotated[ - bool | None, Field(description='Whether this slot is required for a valid manifest.') - ] = False - min: Annotated[ - int | None, Field(description='Minimum count for repeatable / pool slots.', ge=0) - ] = None - max: Annotated[ - int | None, Field(description='Maximum count for repeatable / pool slots.', ge=1) - ] = None - max_chars: Annotated[ - int | None, - Field( - description="Per-slot character limit. Valid only when `asset_type` is `text`, `markdown`, or `brief`. Mutually exclusive with `max_size_kb` (which applies to binary asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - max_size_kb: Annotated[ - int | None, - Field( - description="Per-slot file size limit in kilobytes. Valid only when `asset_type` is `image`, `video`, `audio`, or `zip`. Mutually exclusive with `max_chars` (which applies to text asset types). Schema enforces via if/then so a producer can't set both on the same slot.", - ge=1, - ), - ] = None - logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='When `asset_group_id` is `logo`, renderer-facing brand.json logo slots acceptable for this format slot. Producers selecting from brand.json SHOULD prefer `logos[]` entries whose `slots[]` intersects this list, then apply `visual_guidelines.logo_usage_rules[]`.' - ), - ] = None - required_logo_slots: Annotated[ - list[LogoSlot] | None, - Field( - description='Subset of `logo_slots` for which this format expects explicit logo coverage. A manifest or brand-derived logo pool SHOULD include at least one usable logo for each required slot; if coverage is missing, builders SHOULD surface a validation warning or approval mapping instead of guessing from prose.' - ), - ] = None - description: Annotated[ - str | None, - Field(description='Human-readable description of what the slot expects from the buyer.'), - ] = None - consumed_for_production: Annotated[ - bool | None, - Field( - description="Dispatch hint for `build_creative` and v1↔v2 wire translators: when `true`, the slot's value is consumed as INPUT to a production step (host-read script, brief copy fed to generative synthesis, catalog feed driving per-SKU rendering) and is not rendered verbatim. When `false` (default), the slot's value is rendered verbatim on the placement (image bytes, video file, display tag).\n\nMotivates the v1↔v2 dispatch table: pre-v2 buyers shipped production-consumed inputs separately in a `inputs` map on the build_creative request; v2 collapses inputs and rendered assets into a single `assets` map keyed by `asset_group_id`. SDK translators between v1 and v2 use this flag per canonical to know which assets in the v2 manifest map back to v1 `inputs` vs v1 `assets`. Without the per-slot flag the dispatch table lives in adopter code and every SDK gets it slightly different.\n\nProducers SHOULD set this explicitly on slots whose consumption pattern isn't obvious (host-read scripts on `audio_hosted`, briefs on generative `video_hosted`, catalog feeds on `sponsored_placement`). For canonicals where every slot is render-verbatim (`image`, `display_tag`, `video_vast`), the default `false` is sufficient and the flag MAY be omitted." - ), - ] = False - - -class Params(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions1(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot14(Slot): - pass - - -class Params14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot14] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection13] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions2(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params14, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot15(Slot): - pass - - -class Params15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot15] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection14] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions3(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params15, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot16(Slot): - pass - - -class Params16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot16] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection15] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions4(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params16, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot17(Slot): - pass - - -class Params17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot17] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection16] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions5(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params17, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot18(Slot): - pass - - -class Params18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot18] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection17] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions6(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params18, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot19(Slot): - pass - - -class Params19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot19] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection18] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions7(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params19, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot20(Slot): - pass - - -class Params20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot20] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection19] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions8(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params20, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot21(Slot): - pass - - -class Params21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot21] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection20] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions9(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params21, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot22(Slot): - pass - - -class Params22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot22] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection21] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions10(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params22, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot23(Slot): - pass - - -class Params23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot23] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection22] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions11(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params23, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot24(Slot): - pass - - -class Params24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot24] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection23] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions12(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params24, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions13(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['custom'] = 'custom' - params: Annotated[ - dict[str, Any], - Field( - description="Custom shape's params. Validated against the schema fetched from `format_schema.uri` at the cached `format_schema.digest`." - ), - ] - - -class FormatOptions( - RootModel[ - FormatOptions1 - | FormatOptions2 - | FormatOptions3 - | FormatOptions4 - | FormatOptions5 - | FormatOptions6 - | FormatOptions7 - | FormatOptions8 - | FormatOptions9 - | FormatOptions10 - | FormatOptions11 - | FormatOptions12 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions1 - | FormatOptions2 - | FormatOptions3 - | FormatOptions4 - | FormatOptions5 - | FormatOptions6 - | FormatOptions7 - | FormatOptions8 - | FormatOptions9 - | FormatOptions10 - | FormatOptions11 - | FormatOptions12 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot25(Slot): - pass - - -class Params25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot25] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection24] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions15(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params25, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot26(Slot): - pass - - -class Params26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot26] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection25] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions16(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params26, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot27(Slot): - pass - - -class Params27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot27] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection26] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions17(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params27, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot28(Slot): - pass - - -class Params28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot28] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection27] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions18(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params28, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot29(Slot): - pass - - -class Params29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot29] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection28] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions19(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params29, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot30(Slot): - pass - - -class Params30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot30] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection29] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions20(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params30, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot31(Slot): - pass - - -class Params31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot31] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection30] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions21(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params31, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot32(Slot): - pass - - -class Params32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot32] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection31] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions22(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params32, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot33(Slot): - pass - - -class Params33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot33] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection32] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions23(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params33, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot34(Slot): - pass - - -class Params34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot34] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection33] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions24(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params34, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot35(Slot): - pass - - -class Params35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot35] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection34] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions25(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params35, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot36(Slot): - pass - - -class Params36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot36] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection35] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions26(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params36, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions14( - RootModel[ - FormatOptions15 - | FormatOptions16 - | FormatOptions17 - | FormatOptions18 - | FormatOptions19 - | FormatOptions20 - | FormatOptions21 - | FormatOptions22 - | FormatOptions23 - | FormatOptions24 - | FormatOptions25 - | FormatOptions26 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions15 - | FormatOptions16 - | FormatOptions17 - | FormatOptions18 - | FormatOptions19 - | FormatOptions20 - | FormatOptions21 - | FormatOptions22 - | FormatOptions23 - | FormatOptions24 - | FormatOptions25 - | FormatOptions26 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - Sequence[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions14] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class Adjustment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: PriceAdjustmentKind - name: Annotated[ - str, - Field( - description='Specific adjustment name. Use well-known values where applicable for interoperability.', - examples=[ - 'ad_serving', - 'data_targeting', - 'brand_safety', - 'volume', - 'negotiated', - 'early_booking', - 'agency', - 'intermediary', - 'cash_discount', - 'early_payment', - ], - max_length=64, - ), - ] - rate: Annotated[ - float | None, - Field( - description='Adjustment as a decimal proportion (e.g., 0.15 for 15%). Always positive — kind determines the economic effect. Mutually exclusive with amount.', - gt=0.0, - lt=1.0, - ), - ] = None - amount: Annotated[ - float | None, - Field( - description="Adjustment as a fixed monetary amount in the pricing option's currency. Always positive — kind determines the economic effect. Mutually exclusive with rate.", - gt=0.0, - ), - ] = None - description: Annotated[ - str | None, - Field( - description="Human-readable description of this adjustment (e.g., 'Malstaffel 12x', '2% Skonto 10 Tage')", - max_length=256, - ), - ] = None - beneficiary: Annotated[ - str | None, - Field( - description="Identifies who receives this adjustment's value. For commissions, the intermediary (e.g., a sellers.json domain, an AdCP account ID, or a human-readable party name). Optional but recommended for multi-intermediary transparency.", - max_length=256, - ), - ] = None - - -class PriceBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - list_price: Annotated[ - float, - Field( - description='Rate card or base price before any adjustments. The starting point from which fixed_price is derived by applying fee and discount adjustments sequentially.', - gt=0.0, - ), - ] - adjustments: Annotated[ - list[Adjustment], - Field( - description="Ordered list of price adjustments. Fee and discount adjustments walk list_price to fixed_price — fees increase the running price, discounts reduce it. Commission and settlement adjustments are disclosed for transparency but do not affect the buyer's committed price.", - max_length=20, - min_length=1, - ), - ] - - -class PricingOptions1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown2(PriceBreakdown): - pass - - -class PricingOptions2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown2 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown3(PriceBreakdown): - pass - - -class PricingOptions3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown3 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown4(PriceBreakdown): - pass - - -class PricingOptions4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown4 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown5(PriceBreakdown): - pass - - -class PricingOptions5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown5 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class Parameters4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str, - Field( - description='Target demographic code within the specified demographic_system (e.g., P18-49 for Nielsen, ABC1 Adults for BARB)' - ), - ] - min_points: Annotated[float | None, Field(description='Minimum GRPs/TRPs required', ge=0.0)] = ( - None - ) - - -class PriceBreakdown6(PriceBreakdown): - pass - - -class PricingOptions6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters4, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown6 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown7(PriceBreakdown): - pass - - -class PricingOptions7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown7 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown8(PriceBreakdown): - pass - - -class PricingOptions8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters5 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown8 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown9(PriceBreakdown): - pass - - -class PricingOptions9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters6, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown9 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions( - RootModel[ - PricingOptions1 - | PricingOptions2 - | PricingOptions3 - | PricingOptions4 - | PricingOptions5 - | PricingOptions6 - | PricingOptions7 - | PricingOptions8 - | PricingOptions9 - ] -): - root: Annotated[ - PricingOptions1 - | PricingOptions2 - | PricingOptions3 - | PricingOptions4 - | PricingOptions5 - | PricingOptions6 - | PricingOptions7 - | PricingOptions8 - | PricingOptions9, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' - geo_level: GeographicTargetingLevel - system: Annotated[ - str | None, - Field( - description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." - ), - ] = None - - -class Dimensions10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' - device_type: DeviceType - - -class Dimensions11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['device_platform'], Field(description='Dimension family discriminator.') - ] = 'device_platform' - device_platform: DevicePlatform - - -class Dimensions12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' - audience_id: Annotated[ - str, Field(description='Audience segment identifier for this forecast row.') - ] - audience_source: AudienceSource - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name.') - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent439 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction229] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure220 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent440 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent441 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction230(Jurisdiction229): - pass - - -class Disclosure221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction230] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy220 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem220] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark220] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure221 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem220] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance220 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo26 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride26 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor7, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[Dimensions8 | Dimensions | Dimensions10 | Dimensions11 | Dimensions12 | Dimensions13] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent442 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent443 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction231(Jurisdiction229): - pass - - -class Disclosure222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction231] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy221 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem221] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark221] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure222 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem221] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance221 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo27 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride27 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement(AdCPBaseModel): - vendors: Annotated[ - list[Vendor8] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent444 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent445 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction232(Jurisdiction229): - pass - - -class Disclosure223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction232] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy222 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem222] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark222] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure223 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem222] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance222 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo28 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride28 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor9, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MakegoodPolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_remedies: Annotated[ - list[MakegoodRemedy], - Field( - description='Remedy types the seller supports. Ordered by seller preference (first = preferred). Seller proposes from this list when a breach occurs; buyer accepts or disputes.', - min_length=1, - ), - ] - - -class MeasurementTerms(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent446 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent447 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction233(Jurisdiction229): - pass - - -class Disclosure224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction233] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy223 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem223] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark223] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure224 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem223] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance223 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo29 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride29 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor10, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: MediaBuyValidAction - modes: Annotated[ - list[MediaBuyActionMode], - Field( - description='Modes available for this action on this product. A product may declare multiple modes (for example `self_serve` within tolerances, escalating to `requires_approval` outside) — the buy-side `available_actions[].mode` resolves to the singular mode in effect at mutation time. SDKs that see multiple modes MUST NOT assume which one will fire; they must read the resolved `mode` on the buy.', - min_length=1, - ), - ] - allowed_statuses: Annotated[ - list[MediaBuyStatus] | None, - Field( - description='Media buy statuses in which this action is permitted. When absent, the action is permitted in all non-terminal statuses (`pending_creatives`, `pending_start`, `active`, `paused`).', - min_length=1, - ), - ] = None - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this product. Absence means no commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). When present, the named term governs cancellation policy, makegoods, or other commercial remedies tied to this action. Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class EmbeddedProvenanceItem224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent448 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent449 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction234(Jurisdiction229): - pass - - -class Disclosure225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction234] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy224 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem224] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark224] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure225 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem224] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance224 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo30 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride30 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor11, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - co_branding: CoBrandingRequirement - landing_page: LandingPageRequirement - templates_available: Annotated[ - bool, Field(description='Whether creative templates are provided') - ] - provenance_required: Annotated[ - bool | None, - Field( - description='Whether creatives must include provenance metadata. When true, the seller requires buyers to attach provenance declarations to creative submissions. The seller may independently verify claims via get_creative_features.' - ), - ] = None - provenance_requirements: Annotated[ - ProvenanceRequirements | None, - Field( - description='Structured provenance requirements for creatives. Refines `provenance_required`: when `provenance_required` is true, the fields in this object specify which provenance features the seller requires. When `provenance_required` is false or absent, this object SHOULD be absent; if present, receivers MUST ignore it. Existing seller agents that do not read this object are unaffected; the wire shape does not change for them. Sellers that publish a requirement here MUST enforce it on creative submission: a `sync_creatives` request that omits a required field is rejected with the corresponding `PROVENANCE_*` error code (see error-code.json), and a creative whose provenance claim is contradicted by an independent verification (`get_creative_features` against a governance agent the seller operates or has allowlisted via `accepted_verifiers`) is rejected with `PROVENANCE_CLAIM_CONTRADICTED`. This is the structural-rejection surface; the truth-of-claim surface lives in `get_creative_features`. Field-level requirements are seller-enforced — JSON Schema validation does not check them.' - ), - ] = None - accepted_verifiers: Annotated[ - list[AcceptedVerifier] | None, - Field( - description='Governance agents the seller operates, has allowlisted, or otherwise trusts to verify provenance claims via `get_creative_features`. Buyers attaching a `verify_agent` pointer on `embedded_provenance[]` or `watermarks[]` MUST select an `agent_url` that appears in this list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments) - the buyer is *representing* that they used a verifier the seller will recognize, not asserting unilateral routing. Sellers MUST reject `sync_creatives` submissions whose `verify_agent.agent_url` does not match any entry here with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. The seller is the verifier-of-record: it is the seller, not the buyer, that decides which agent it will call. Publishing the list lets buyers pre-flight their creative shape against `get_products` and lets multiple buyers converge on the same verifier without coordinating with each other.', - min_length=1, - ), - ] = None - - -class IncludedSignal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric], - Field( - description='Metric kinds this product can optimize for. Buyers should only request metric goals for kinds listed here. **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — declare vendor-attested attention/quality metrics via `vendor_metric_optimization.supported_metrics[]` with an explicit vendor binding instead. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind.', - min_length=1, - ), - ] - supported_reach_units: Annotated[ - list[ReachUnit] | None, - Field( - description="Reach units this product can optimize for. Required when supported_metrics includes 'reach'. Buyers must set reach_unit to a value in this list on reach optimization goals — sellers reject unsupported values.", - min_length=1, - ), - ] = None - supported_view_durations: Annotated[ - list[SupportedViewDuration] | None, - Field( - description="Video view duration thresholds (in seconds) this product supports for completed_views goals. Only relevant when supported_metrics includes 'completed_views'. When absent, the seller uses their platform default. Buyers must set view_duration_seconds to a value in this list — sellers reject unsupported values." - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for metric goals on this product. Values match target.kind on the optimization goal. Only these target kinds are accepted — goals with unlisted target kinds will be rejected. When omitted, buyers can set target-less metric goals (maximize volume within budget) but cannot set specific targets.' - ), - ] = None - - -class EmbeddedProvenanceItem225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent450 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent451 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction235(Jurisdiction229): - pass - - -class Disclosure226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction235] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy225 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem225] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark225] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure226 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem225] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance225 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo31 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride31 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor12, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric1], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue18] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_sources: Annotated[ - list[ActionSource] | None, - Field( - description="Action sources relevant to this product (e.g. a retail media product might have 'in_store' and 'website', while a display product might only have 'website')", - min_length=1, - ), - ] = None - supported_targets: Annotated[ - list[SupportedTarget6] | None, - Field( - description='Target kinds available for event goals on this product. Values match target.kind on the optimization goal. cost_per: target cost per conversion event. per_ad_spend: target return on ad spend (requires value_field on event sources). maximize_value: maximize total conversion value without a specific ratio target (requires value_field). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes conversion count within budget — no declaration needed for that mode. When omitted, buyers can still set target-less event goals.', - min_length=1, - ), - ] = None - platform_managed: Annotated[ - bool | None, - Field( - description="Whether the seller provides its own always-on measurement (e.g. Amazon sales attribution for Amazon advertisers). When true, sync_event_sources response will include seller-managed event sources with managed_by='seller'." - ), - ] = None - - -class EmbeddedProvenanceItem226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent452 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent453 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction236(Jurisdiction229): - pass - - -class Disclosure227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction236] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy226 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem226] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark226] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure227 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem226] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance226 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent454 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent455 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction237(Jurisdiction229): - pass - - -class Disclosure228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction237] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy227 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem227] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark227] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure228 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem227] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance227 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent456 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent457 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction238(Jurisdiction229): - pass - - -class Disclosure229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction238] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy228 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem228] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark228] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure229 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem228] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance228 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class ContentRating(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - system: ContentRatingSystem - rating: Annotated[ - str, Field(description="Rating value within the system (e.g., 'TV-PG', 'R', 'explicit')") - ] - - -class Special(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, Field(description="Name of the event (e.g., 'Olympics 2028', 'Super Bowl LXI')") - ] - category: SpecialCategory | None = None - starts: Annotated[ - AwareDatetime | None, Field(description='When the event starts (ISO 8601)') - ] = None - ends: Annotated[ - AwareDatetime | None, - Field(description='When the event ends (ISO 8601). Omit for single-day events.'), - ] = None - - -class GuestTalentItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - role: TalentRole - name: Annotated[str, Field(description="Person's name as credited on the collection")] - brand_url: Annotated[ - AnyUrl | None, - Field( - description="URL to this person's brand.json entry. Enables buyer agents to evaluate the talent's brand identity and associations." - ), - ] = None - - -class DerivativeOf(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - installment_id: Annotated[ - str, Field(description='The source installment this content is derived from') - ] - type: DerivativeType - - -class Installment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="Provider's agent URL from the registry. Canonical identifier for this TMP provider." - ), - ] - context_match: Annotated[ - bool | None, - Field(description='Whether this provider handles context match for this product.'), - ] = False - identity_match: Annotated[ - bool | None, - Field(description='Whether this provider handles identity match for this product.'), - ] = False - countries: Annotated[ - list[Country] | None, - Field( - description="ISO 3166-1 alpha-2 country codes this provider serves for identity match. The router uses this to select the correct regional provider based on the request's country field. Required when identity_match is true.", - min_length=1, - ), - ] = None - uid_types: Annotated[ - list[UIDType] | None, - Field( - description="Identity types this regional provider can resolve. The router filters providers whose uid_types includes the request's uid_type. Required when identity_match is true.", - min_length=1, - ), - ] = None - - -class TrustedMatch(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class Products(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId], - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] - format_options: Annotated[ - list[FormatOptions] | None, - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[Placement] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot37(Slot): - pass - - -class Params37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot37] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection36] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions29(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params37, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot38(Slot): - pass - - -class Params38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot38] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection37] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions30(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params38, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot39(Slot): - pass - - -class Params39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot39] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection38] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions31(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params39, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot40(Slot): - pass - - -class Params40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot40] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection39] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions32(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params40, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot41(Slot): - pass - - -class Params41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot41] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection40] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions33(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params41, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot42(Slot): - pass - - -class Params42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot42] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection41] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions34(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params42, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot43(Slot): - pass - - -class Params43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot43] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection42] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions35(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params43, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot44(Slot): - pass - - -class Params44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot44] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection43] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions36(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params44, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot45(Slot): - pass - - -class Params45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot45] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection44] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions37(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params45, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot46(Slot): - pass - - -class Params46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot46] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection45] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions38(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params46, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot47(Slot): - pass - - -class Params47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot47] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection46] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions39(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params47, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot48(Slot): - pass - - -class Params48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot48] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection47] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions40(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params48, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions28( - RootModel[ - FormatOptions29 - | FormatOptions30 - | FormatOptions31 - | FormatOptions32 - | FormatOptions33 - | FormatOptions34 - | FormatOptions35 - | FormatOptions36 - | FormatOptions37 - | FormatOptions38 - | FormatOptions39 - | FormatOptions40 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions29 - | FormatOptions30 - | FormatOptions31 - | FormatOptions32 - | FormatOptions33 - | FormatOptions34 - | FormatOptions35 - | FormatOptions36 - | FormatOptions37 - | FormatOptions38 - | FormatOptions39 - | FormatOptions40 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot49(Slot): - pass - - -class Params49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot49] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection48] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions43(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params49, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot50(Slot): - pass - - -class Params50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot50] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection49] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions44(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params50, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot51(Slot): - pass - - -class Params51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot51] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection50] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions45(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params51, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot52(Slot): - pass - - -class Params52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot52] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection51] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions46(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params52, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot53(Slot): - pass - - -class Params53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot53] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection52] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions47(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params53, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot54(Slot): - pass - - -class Params54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot54] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection53] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions48(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params54, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot55(Slot): - pass - - -class Params55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot55] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection54] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions49(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params55, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot56(Slot): - pass - - -class Params56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot56] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection55] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions50(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params56, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot57(Slot): - pass - - -class Params57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot57] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection56] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions51(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params57, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot58(Slot): - pass - - -class Params58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot58] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection57] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions52(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params58, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot59(Slot): - pass - - -class Params59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot59] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection58] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions53(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params59, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot60(Slot): - pass - - -class Params60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot60] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection59] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions54(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params60, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions42( - RootModel[ - FormatOptions43 - | FormatOptions44 - | FormatOptions45 - | FormatOptions46 - | FormatOptions47 - | FormatOptions48 - | FormatOptions49 - | FormatOptions50 - | FormatOptions51 - | FormatOptions52 - | FormatOptions53 - | FormatOptions54 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions43 - | FormatOptions44 - | FormatOptions45 - | FormatOptions46 - | FormatOptions47 - | FormatOptions48 - | FormatOptions49 - | FormatOptions50 - | FormatOptions51 - | FormatOptions52 - | FormatOptions53 - | FormatOptions54 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions42] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown10(PriceBreakdown): - pass - - -class PricingOptions11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown10 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown11(PriceBreakdown): - pass - - -class PricingOptions12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown11 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown12(PriceBreakdown): - pass - - -class PricingOptions13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown12 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown13(PriceBreakdown): - pass - - -class PricingOptions14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown13 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown14(PriceBreakdown): - pass - - -class PricingOptions15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters7, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown14 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown15(PriceBreakdown): - pass - - -class PricingOptions16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters4, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown15 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown16(PriceBreakdown): - pass - - -class PricingOptions17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown16 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown17(PriceBreakdown): - pass - - -class PricingOptions18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters9 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown17 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown18(PriceBreakdown): - pass - - -class PricingOptions19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters10, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown18 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions10( - RootModel[ - PricingOptions11 - | PricingOptions12 - | PricingOptions13 - | PricingOptions14 - | PricingOptions15 - | PricingOptions16 - | PricingOptions17 - | PricingOptions18 - | PricingOptions19 - ] -): - root: Annotated[ - PricingOptions11 - | PricingOptions12 - | PricingOptions13 - | PricingOptions14 - | PricingOptions15 - | PricingOptions16 - | PricingOptions17 - | PricingOptions18 - | PricingOptions19, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions14(Dimensions8): - pass - - -class Dimensions16(Dimensions10): - pass - - -class Dimensions17(Dimensions11): - pass - - -class Dimensions18(Dimensions12): - pass - - -class EmbeddedProvenanceItem229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent458 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent459 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction239(Jurisdiction229): - pass - - -class Disclosure230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction239] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance229(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy229 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem229] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark229] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure230 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem229] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance229 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo32 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor13(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride32 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor13 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent460 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent461 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction240(Jurisdiction229): - pass - - -class Disclosure231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction240] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance230(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy230 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem230] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark230] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure231 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem230] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance230 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo33 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor14(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride33 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor14, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions14 | Dimensions15 | Dimensions16 | Dimensions17 | Dimensions18 | Dimensions19 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics3, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability7 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue5] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point2], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent462 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent463 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction241(Jurisdiction229): - pass - - -class Disclosure232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction241] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance231(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy231 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem231] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark231] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure232 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem231] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance231 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo34 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor15(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride34 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement1(AdCPBaseModel): - vendors: Annotated[ - list[Vendor15] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent464 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent465 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction242(Jurisdiction229): - pass - - -class Disclosure233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction242] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy232 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem232] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark232] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure233 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem232] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance232 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo35 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor16(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride35 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor16, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement1 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent466 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent467 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction243(Jurisdiction229): - pass - - -class Disclosure234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction243] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy233 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem233] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark233] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure234 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem233] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance233 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo36 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor17(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride36 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor17, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction1(AllowedAction): - pass - - -class EmbeddedProvenanceItem234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent468 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent469 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction244(Jurisdiction229): - pass - - -class Disclosure235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction244] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy234 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem234] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark234] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure235 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem234] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance234 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo37 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride37 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor18, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric1] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown1 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy2(CreativePolicy): - pass - - -class IncludedSignal1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption15] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization1(MetricOptimization): - pass - - -class EmbeddedProvenanceItem235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent470 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent471 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction245(Jurisdiction229): - pass - - -class Disclosure236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction245] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy235 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem235] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark235] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure236 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem235] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance235 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo38 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor19(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride38 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor19, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric3], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue19] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking3(ConversionTracking): - pass - - -class EmbeddedProvenanceItem236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent472 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent473 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction246(Jurisdiction229): - pass - - -class Disclosure237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction246] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy236 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem236] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark236] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure237 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem236] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance236 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image1 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent474 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent475 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction247(Jurisdiction229): - pass - - -class Disclosure238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction247] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance237(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy237 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem237] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark237] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure238 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem237] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance237 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent476 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent477 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction248(Jurisdiction229): - pass - - -class Disclosure239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction248] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy238 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem238] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark238] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure239 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem238] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance238 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage1 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage1] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines1 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider6(Provider): - pass - - -class TrustedMatch3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider6] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class Products1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty8], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] = None - format_options: Annotated[ - list[FormatOptions28], - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] - placements: Annotated[ - list[Placement2] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions10], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast1 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement1 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement1 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms1 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard1] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy2 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction1] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities1, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy2 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal1] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption1] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules1 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization1 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization3 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness1 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking3 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch1 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard1 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed1 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment1] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch3 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class DaypartTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - days: Annotated[ - list[DayOfWeek], - Field( - description='Days of week this window applies to. Use multiple days for compact targeting (e.g., monday-friday in one object).', - min_length=1, - ), - ] - start_hour: Annotated[ - int, - Field( - description='Start hour (inclusive), 0-23 in 24-hour format. 0 = midnight, 6 = 6:00am, 18 = 6:00pm.', - ge=0, - le=23, - ), - ] - end_hour: Annotated[ - int, - Field( - description='End hour (exclusive), 1-24 in 24-hour format. 10 = 10:00am, 24 = midnight. Must be greater than start_hour.', - ge=1, - le=24, - ), - ] - label: Annotated[ - str | None, - Field( - description="Optional human-readable name for this time window (e.g., 'Morning Drive', 'Prime Time')" - ), - ] = None - - -class Dimensions20(Dimensions8): - pass - - -class Dimensions22(Dimensions10): - pass - - -class Dimensions23(Dimensions11): - pass - - -class Dimensions24(Dimensions12): - pass - - -class EmbeddedProvenanceItem239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent478 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent479 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction249(Jurisdiction229): - pass - - -class Disclosure240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction249] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy239 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem239] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark239] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure240 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem239] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance239 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo39 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor20(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride39 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor20 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent480 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent481 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction250(Jurisdiction229): - pass - - -class Disclosure241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction250] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy240 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem240] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark240] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure241 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem240] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance240 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride40(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo40 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor21(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride40 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor21, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions20 | Dimensions21 | Dimensions22 | Dimensions23 | Dimensions24 | Dimensions25 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics4, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability8 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue6] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point3], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Allocation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[ - str, Field(description='ID of the product (must reference a product in the products array)') - ] - allocation_percentage: Annotated[ - float, - Field( - description='Percentage of total budget allocated to this product (0-100)', - ge=0.0, - le=100.0, - ), - ] - pricing_option_id: Annotated[ - str | None, - Field(description="Recommended pricing option ID from the product's pricing_options array"), - ] = None - rationale: Annotated[ - str | None, - Field(description='Explanation of why this product and allocation are recommended'), - ] = None - sequence: Annotated[ - int | None, - Field(description='Optional ordering hint for multi-line-item plans (1-based)', ge=1), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description="Categorical tags for this allocation (e.g., 'desktop', 'german', 'mobile') - useful for grouping/filtering allocations by dimension" - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight start date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description='Recommended flight end date/time for this allocation in ISO 8601 format. Allows publishers to propose per-flight scheduling within a proposal. When omitted, the allocation applies to the full campaign date range.' - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Recommended time windows for this allocation in spot-plan proposals.', - min_length=1, - ), - ] = None - forecast: Annotated[ - Forecast2 | None, - Field( - description='Forecasted delivery metrics for this allocation', title='Delivery Forecast' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Dimensions26(Dimensions8): - pass - - -class Dimensions28(Dimensions10): - pass - - -class Dimensions29(Dimensions11): - pass - - -class Dimensions30(Dimensions12): - pass - - -class EmbeddedProvenanceItem241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent482 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent483 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction251(Jurisdiction229): - pass - - -class Disclosure242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction251] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy241 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem241] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark241] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure242 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem241] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance241 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride41(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo41 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride41 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor22 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent484 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent485 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction252(Jurisdiction229): - pass - - -class Disclosure243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction252] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy242 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem242] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark242] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure243 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem242] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance242 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride42(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo42 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride42 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor23, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions26 | Dimensions27 | Dimensions28 | Dimensions29 | Dimensions30 | Dimensions31 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics5, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability9 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue7] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point4], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Proposal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - proposal_id: Annotated[ - str, - Field( - description='Unique identifier for this proposal. Used to finalize a draft proposal and to execute a committed proposal via create_media_buy.', - max_length=255, - ), - ] - name: Annotated[ - str, Field(description='Human-readable name for this media plan proposal', max_length=500) - ] - description: Annotated[ - str | None, - Field( - description='Explanation of the proposal strategy and what it achieves', max_length=2000 - ), - ] = None - allocations: Annotated[ - list[Allocation], - Field( - description='Budget allocations across products. Allocation percentages MUST sum to 100. Publishers are responsible for ensuring the sum equals 100; buyers SHOULD validate this before execution.', - min_length=1, - ), - ] - proposal_status: Annotated[ - ProposalStatus | None, - Field( - description="Lifecycle status of this proposal and the per-proposal source of truth for whether finalization is required before create_media_buy. When absent, the proposal is ready to buy (backward compatible). 'draft' means indicative pricing — finalize via refine before purchasing. 'committed' means firm pricing with inventory reserved until expires_at and executable via create_media_buy.", - title='Proposal Status', - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this proposal expires and can no longer be executed. For draft proposals, indicates when indicative pricing becomes stale. For committed proposals, indicates when the inventory hold lapses — the buyer must call create_media_buy before this time.' - ), - ] = None - insertion_order: Annotated[ - InsertionOrder | None, - Field( - description='Formal insertion order attached to a committed proposal. Present when the seller requires a signed agreement before the media buy can proceed. The buyer references the io_id in io_acceptance on create_media_buy.', - title='Insertion Order', - ), - ] = None - total_budget_guidance: Annotated[ - TotalBudgetGuidance | None, Field(description='Optional budget guidance for this proposal') - ] = None - brief_alignment: Annotated[ - str | None, - Field( - description='Explanation of how this proposal aligns with the campaign brief', - max_length=2000, - ), - ] = None - forecast: Annotated[ - Forecast3 | None, - Field( - description='Aggregate forecasted delivery metrics for the entire proposal. When both proposal-level and allocation-level forecasts are present, the proposal-level forecast is authoritative for total delivery estimation.', - title='Delivery Forecast', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError13 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig15 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - products: Annotated[ - list[Products | Products1] | None, Field(description='Array of matching products') - ] = None - extensions: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^https?://[^@]+@sha256:[a-f0-9]{64}$')], Extensions] | None, - Field( - description='Bundled platform-extension definitions referenced by any product in `products`. Keyed by `@` (e.g., `https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel@sha256:abc...`). When present, lets buyers resolve `platform_extensions` references on product format declarations without a separate fetch. Buyer SDKs cache by URI@digest; subsequent get_products responses MAY omit definitions the buyer already has cached and rely on the digest match. Each value is an extension definition with `extends` (the canonical concept it extends, e.g., `tracking`), `fields` (the schema for additional fields the extension contributes), `version`, and optional `description`.' - ), - ] = None - proposals: Annotated[ - list[Proposal] | None, - Field( - description='Optional array of proposed media plans with budget allocations across products. Publishers include proposals when they can provide strategic guidance based on the brief. Proposals are actionable - buyers can refine them via follow-up get_products calls within the same session, or execute them directly via create_media_buy.' - ), - ] = None - errors: Annotated[ - list[Error] | None, - Field(description='Task-specific errors and warnings (e.g., product filtering issues)'), - ] = None - property_list_applied: Annotated[ - bool | None, - Field( - description='[AdCP 3.0] Indicates whether property_list filtering was applied. True if the agent filtered products based on the provided property_list. Absent or false if property_list was not provided or not supported by this agent.' - ), - ] = None - catalog_applied: Annotated[ - bool | None, - Field( - description='Whether the seller filtered results based on the provided catalog. True if the seller matched catalog items against its inventory. Absent or false if no catalog was provided or the seller does not support catalog matching.' - ), - ] = None - refinement_applied: Annotated[ - list[RefinementApplied4] | None, - Field( - description="Seller's response to each change request in the refine array, matched by position. Each entry acknowledges whether the corresponding ask was applied, partially applied, or unable to be fulfilled. MUST contain the same number of entries in the same order as the request's refine array. Only present when the request used buying_mode: 'refine'. Each entry MUST echo the request entry's scope and — for product and proposal scopes — the matching id field (product_id or proposal_id), so orchestrators can cross-validate alignment." - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the seller could not finish within the buyer's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - filter_diagnostics: Annotated[ - FilterDiagnostics | None, - Field( - description="Optional non-fatal diagnostic block describing how the request's `filters` narrowed the candidate set. Use this to disambiguate empty/small result lists between 'no inventory matches the brief' and 'a specific filter excluded everything', without breaking the filter-not-fail convention (sellers still silently exclude unmatched products; this block is observability, not error reporting). Sellers MAY populate this when meaningful narrowing occurred; buyers MAY use it for triage UX without depending on its presence. Counts only — products are not enumerated by name to avoid leaking competitive intelligence about adjacent campaigns or seller inventory. `total_candidates` and `excluded_by` are independently optional — sellers whose baseline candidate set size is sensitive MAY emit `excluded_by` without `total_candidates`, or vice versa.", - examples=[ - { - 'semantics': 'only', - 'total_candidates': 47, - 'excluded_by': { - 'required_metrics': {'count': 31, 'values': ['completed_views']}, - 'required_geo_targeting': {'count': 9}, - 'pricing_currencies': {'count': 3, 'values': ['USD']}, - 'budget_range': {'count': 7}, - }, - } - ], - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale product feed state used to compose this response. Sellers that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so buyers can cache and probe later. Buyers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A buyer caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, buying_mode, filters, property_list, catalog) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer, including product pricing_options and nested signal_targeting_options pricing_options. When the seller supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Sellers not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the seller's published rate card; the buyer MAY dedupe under (agent, buying_mode, filters, property_list, catalog) without scoping by account. 'account': this response includes account-specific overrides; the buyer MUST cache the version under (agent, buying_mode, filters, property_list, catalog, account_id). When the request did NOT include `account`, the seller MUST return `cache_scope: 'public'`. When the request included `account`, the seller MUST return either: 'public' (this account prices off the public rate card — buyer dedupes) or 'account' (account-specific overrides exist — buyer caches under the account key). Sellers MAY return 'public' on an account-scoped request that previously had overrides — buyers SHOULD interpret this as a downgrade and drop their account-overlay for the (agent, filters, mode) tuple. Without schema-required cache_scope, a seller silently omitting the field on an account-scoped response would cause buyers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs that validate strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version` (release-precision version negotiation, 3.1). For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 sellers correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = None - unchanged: Annotated[ - Literal[True], - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the seller's current version for the buyer's cache_scope, in which case products[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Buyers receiving unchanged: true MUST NOT mutate their local wholesale product mirror. **One shape per state:** sellers MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries products. Two shapes ({ unchanged: false, products: [...] } vs. { products: [...] }) for the same state would let some sellers always emit the field and some never would, creating an inconsistency the wire shouldn't carry." - ), - ] = True - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot61(Slot): - pass - - -class Params61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot61] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection60] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions57(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params61, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot62(Slot): - pass - - -class Params62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot62] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection61] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions58(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params62, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot63(Slot): - pass - - -class Params63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot63] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection62] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions59(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params63, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot64(Slot): - pass - - -class Params64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot64] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection63] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions60(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params64, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot65(Slot): - pass - - -class Params65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot65] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection64] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions61(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params65, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot66(Slot): - pass - - -class Params66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot66] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection65] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions62(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params66, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot67(Slot): - pass - - -class Params67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot67] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection66] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions63(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params67, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot68(Slot): - pass - - -class Params68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot68] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection67] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions64(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params68, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot69(Slot): - pass - - -class Params69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot69] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection68] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions65(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params69, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot70(Slot): - pass - - -class Params70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot70] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection69] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions66(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params70, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot71(Slot): - pass - - -class Params71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot71] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection70] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions67(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params71, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot72(Slot): - pass - - -class Params72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot72] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection71] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions68(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params72, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions56( - RootModel[ - FormatOptions57 - | FormatOptions58 - | FormatOptions59 - | FormatOptions60 - | FormatOptions61 - | FormatOptions62 - | FormatOptions63 - | FormatOptions64 - | FormatOptions65 - | FormatOptions66 - | FormatOptions67 - | FormatOptions68 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions57 - | FormatOptions58 - | FormatOptions59 - | FormatOptions60 - | FormatOptions61 - | FormatOptions62 - | FormatOptions63 - | FormatOptions64 - | FormatOptions65 - | FormatOptions66 - | FormatOptions67 - | FormatOptions68 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot73(Slot): - pass - - -class Params73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot73] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection72] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions71(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params73, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot74(Slot): - pass - - -class Params74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot74] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection73] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions72(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params74, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot75(Slot): - pass - - -class Params75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot75] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection74] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions73(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params75, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot76(Slot): - pass - - -class Params76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot76] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection75] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions74(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params76, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot77(Slot): - pass - - -class Params77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot77] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection76] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions75(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params77, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot78(Slot): - pass - - -class Params78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot78] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection77] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions76(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params78, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot79(Slot): - pass - - -class Params79(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot79] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection78] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions77(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params79, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot80(Slot): - pass - - -class Params80(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot80] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection79] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions78(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params80, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot81(Slot): - pass - - -class Params81(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot81] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection80] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions79(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params81, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot82(Slot): - pass - - -class Params82(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot82] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection81] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions80(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params82, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot83(Slot): - pass - - -class Params83(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot83] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection82] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions81(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params83, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot84(Slot): - pass - - -class Params84(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot84] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection83] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions82(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params84, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions70( - RootModel[ - FormatOptions71 - | FormatOptions72 - | FormatOptions73 - | FormatOptions74 - | FormatOptions75 - | FormatOptions76 - | FormatOptions77 - | FormatOptions78 - | FormatOptions79 - | FormatOptions80 - | FormatOptions81 - | FormatOptions82 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions71 - | FormatOptions72 - | FormatOptions73 - | FormatOptions74 - | FormatOptions75 - | FormatOptions76 - | FormatOptions77 - | FormatOptions78 - | FormatOptions79 - | FormatOptions80 - | FormatOptions81 - | FormatOptions82 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions70] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown19(PriceBreakdown): - pass - - -class PricingOptions21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown19 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown20(PriceBreakdown): - pass - - -class PricingOptions22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown20 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown21(PriceBreakdown): - pass - - -class PricingOptions23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown21 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown22(PriceBreakdown): - pass - - -class PricingOptions24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown22 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown23(PriceBreakdown): - pass - - -class PricingOptions25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters11, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown23 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown24(PriceBreakdown): - pass - - -class PricingOptions26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters4, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown24 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown25(PriceBreakdown): - pass - - -class PricingOptions27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown25 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown26(PriceBreakdown): - pass - - -class PricingOptions28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters13 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown26 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown27(PriceBreakdown): - pass - - -class PricingOptions29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters14, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown27 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions20( - RootModel[ - PricingOptions21 - | PricingOptions22 - | PricingOptions23 - | PricingOptions24 - | PricingOptions25 - | PricingOptions26 - | PricingOptions27 - | PricingOptions28 - | PricingOptions29 - ] -): - root: Annotated[ - PricingOptions21 - | PricingOptions22 - | PricingOptions23 - | PricingOptions24 - | PricingOptions25 - | PricingOptions26 - | PricingOptions27 - | PricingOptions28 - | PricingOptions29, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions32(Dimensions8): - pass - - -class Dimensions34(Dimensions10): - pass - - -class Dimensions35(Dimensions11): - pass - - -class Dimensions36(Dimensions12): - pass - - -class EmbeddedProvenanceItem243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent486 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent487 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction253(Jurisdiction229): - pass - - -class Disclosure244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction253] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy243 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem243] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark243] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure244 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem243] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance243 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride43(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo43 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor24(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride43 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor24 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent488 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent489 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction254(Jurisdiction229): - pass - - -class Disclosure245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction254] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy244 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem244] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark244] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure245 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem244] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance244 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride44(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo44 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor25(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride44 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue8(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor25, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions32 | Dimensions33 | Dimensions34 | Dimensions35 | Dimensions36 | Dimensions37 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics6, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability10 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue8] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point5], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent490 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent491 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction255(Jurisdiction229): - pass - - -class Disclosure246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction255] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy245 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem245] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark245] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure246 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem245] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance245 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride45(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo45 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor26(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride45 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement2(AdCPBaseModel): - vendors: Annotated[ - list[Vendor26] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent492 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent493 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction256(Jurisdiction229): - pass - - -class Disclosure247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction256] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy246 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem246] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark246] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure247 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem246] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance246 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride46(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo46 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor27(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride46 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor27, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement2 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent494 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent495 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction257(Jurisdiction229): - pass - - -class Disclosure248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction257] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy247 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem247] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark247] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure248 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem247] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance247 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride47(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo47 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor28(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride47 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor28, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction2(AllowedAction): - pass - - -class EmbeddedProvenanceItem248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent496 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent497 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction258(Jurisdiction229): - pass - - -class Disclosure249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction258] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy248 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem248] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark248] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure249 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem248] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance248 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride48(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo48 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor29(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride48 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor29, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric2] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown2 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy3(CreativePolicy): - pass - - -class IncludedSignal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption16] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization2(MetricOptimization): - pass - - -class EmbeddedProvenanceItem249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent498 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent499 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction259(Jurisdiction229): - pass - - -class Disclosure250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction259] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy249 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem249] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark249] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure250 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem249] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance249 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride49(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo49 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor30(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride49 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor30, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric5], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue21] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking4(ConversionTracking): - pass - - -class EmbeddedProvenanceItem250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent500 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent501 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction260(Jurisdiction229): - pass - - -class Disclosure251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction260] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy250 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem250] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark250] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure251 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem250] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance250 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image2 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent502 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent503 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction261(Jurisdiction229): - pass - - -class Disclosure252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction261] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy251 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem251] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark251] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure252 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem251] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance251 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent504 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent505 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction262(Jurisdiction229): - pass - - -class Disclosure253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction262] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance252(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy252 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem252] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark252] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure253 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem252] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance252 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage2 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage2] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines2 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider7(Provider): - pass - - -class TrustedMatch4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider7] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class PartialResults(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty9], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId], - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] - format_options: Annotated[ - list[FormatOptions56] | None, - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] = None - placements: Annotated[ - list[Placement3] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions20], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast4 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement2 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement2 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms2 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard2] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy3 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction2] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities2, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy3 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal2] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption2] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules2 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization2 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization4 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness2 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking4 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch2 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard2 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed2 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment2] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch4 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Slot85(Slot): - pass - - -class Params85(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot85] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection84] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions85(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params85, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot86(Slot): - pass - - -class Params86(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot86] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection85] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions86(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params86, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot87(Slot): - pass - - -class Params87(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot87] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection86] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions87(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params87, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot88(Slot): - pass - - -class Params88(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot88] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection87] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions88(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params88, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot89(Slot): - pass - - -class Params89(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot89] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection88] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions89(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params89, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot90(Slot): - pass - - -class Params90(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot90] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection89] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions90(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params90, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot91(Slot): - pass - - -class Params91(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot91] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection90] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions91(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params91, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot92(Slot): - pass - - -class Params92(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot92] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection91] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions92(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params92, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot93(Slot): - pass - - -class Params93(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot93] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection92] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions93(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params93, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot94(Slot): - pass - - -class Params94(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot94] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection93] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions94(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params94, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot95(Slot): - pass - - -class Params95(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot95] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection94] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions95(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params95, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot96(Slot): - pass - - -class Params96(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot96] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection95] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions96(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params96, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions84( - RootModel[ - FormatOptions85 - | FormatOptions86 - | FormatOptions87 - | FormatOptions88 - | FormatOptions89 - | FormatOptions90 - | FormatOptions91 - | FormatOptions92 - | FormatOptions93 - | FormatOptions94 - | FormatOptions95 - | FormatOptions96 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions85 - | FormatOptions86 - | FormatOptions87 - | FormatOptions88 - | FormatOptions89 - | FormatOptions90 - | FormatOptions91 - | FormatOptions92 - | FormatOptions93 - | FormatOptions94 - | FormatOptions95 - | FormatOptions96 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Slot97(Slot): - pass - - -class Params97(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot97] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection96] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required image width in pixels — use for fixed-size slots (e.g., a 300×250 IAB MREC). For multi-size flexible slots (publisher MREC slot that accepts 300×250 OR 728×90 OR 970×250), use `sizes[]` instead; for responsive slots that adapt to viewport, use `min_width`/`max_width`/`min_height`/`max_height`. The three modes are mutually exclusive — set exactly one of `(width+height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required image height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot. Buyer ships an asset matching one of the listed sizes; SDK validates `assets.image_main.{width,height}` against the list (any-match). Mirrors OpenRTB `banner.format[]` semantics — one declaration with N accepted sizes is cleaner than N format_options entries. Mutually exclusive with `(width, height)` and with `min/max_width` + `min/max_height` ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description="Minimum accepted width in pixels for responsive slots that adapt within a range (e.g., 'any width from 300 to 970'). Use with `max_width` (and optionally `min_height`/`max_height`). Mutually exclusive with `(width, height)` and `sizes[]`.", - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width in pixels for responsive slots. Pair with `min_width`. See `min_width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height in pixels for responsive slots. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height in pixels for responsive slots. Pair with `min_height`.', - ge=1, - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description="Optional aspect ratio constraint (e.g., '1.91:1', '1:1'). When provided alongside `width`/`height`, must agree. When used with `sizes[]` or responsive ranges, narrows accepted entries to those matching the aspect ratio.", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - max_file_size_kb: Annotated[ - int | None, Field(description='Maximum file size in kilobytes.', ge=1) - ] = None - image_formats: Annotated[ - list[ImageFormat] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field(description='Whether the image and its trackers must be served over HTTPS.'), - ] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - body_text_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW'])." - ), - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered asset bytes come from. Single shared enum across all canonicals (`image`, `video_hosted`, `audio_hosted` — replaces the earlier per-canonical `image_source` / `video_source` / `audio_source` fields). `buyer_uploaded` (default): buyer ships a pre-rendered asset. `publisher_host_recorded`: publisher's host records the asset (audio-specific; podcast host-read pattern). `seller_pre_rendered_from_brief`: buyer ships a brief plus structured copy; seller renders ONE asset at sync_creatives or build_creative time (generative-DSP pattern). `seller_human_designed`: seller's design team renders manually from a brief. `agent_synthesized`: AI synthesis pipeline; pair with `synthesis_nondeterministic: true` when the platform cannot guarantee in-spec output (Veo/Sora/Imagen-class). `publisher_owned_reference`: buyer references an existing post or publisher-owned object via a `published_post` slot; the seller resolves and serves the referenced content after authorization/review rather than receiving uploaded bytes.\n\nNot every value is meaningful on every canonical — `publisher_host_recorded` is audio-specific; on `image` or `video_hosted` it has no defined behavior. `publisher_owned_reference` is meaningful only when the product's `slots` declaration accepts a reference asset such as `published_post`. Adopters MUST select a value appropriate to the canonical's asset type. The `slots` declaration is the binding contract for what the buyer ships; `asset_source` is informational and lets buyers understand the production model when picking products." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded assets. When `rejected`, the buyer cannot ship pre-rendered bytes directly — they must use build_creative (or sync_creatives with brief inputs or reference assets) so the seller produces or resolves the asset. Combined with `asset_source`, lets a product declare 'I produce assets from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`) or 'I accept existing post references, not uploaded bytes' (asset_source=`publisher_owned_reference`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions99(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image'] = 'image' - params: Annotated[ - Params97, - Field( - description='Static image creative format. Slots: `image_main` (image asset, file or hosted URL), optional `headline` (text), `body_text` (text), `cta` (text/enum), `landing_page_url` (url). Tracking model: impression pixel + click URL via universal_macros, with optional viewability pixel. Distinct from `html5` (interactive bundles) and `display_tag` (third-party served). AR/dimensions narrow to specific sizes via product parameters — covers IAB display sizes (300x250, 728x90, 970x250, etc.) without a separate iab_size enum.', - title='Canonical Format: Image', - ), - ] - - -class Slot98(Slot): - pass - - -class Params98(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot98] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection97] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required banner width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required banner height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description='List of accepted (width, height) pairs for a multi-size flexible slot (publisher banner that accepts 300×250 OR 728×90 OR 970×250). Mirrors OpenRTB `banner.format[]`. Mutually exclusive with `(width, height)` and with responsive ranges.', - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive HTML5 banners that adapt within a range. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive HTML5 banners. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive HTML5 banners. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive HTML5 banners. Pair with `min_height`.', - ge=1, - ), - ] = None - max_initial_load_kb: Annotated[ - int | None, - Field( - description='Maximum initial-load file size (zip + above-the-fold assets) in kilobytes. IAB display standards: 200 KB for fixed sizes, 100 KB for mobile.', - ge=1, - ), - ] = None - max_polite_load_kb: Annotated[ - int | None, - Field( - description='Maximum polite-load file size after host-initiated subload, in kilobytes. IAB display standards: 500 KB for fixed sizes.', - ge=1, - ), - ] = None - host_initiated_subload: Annotated[ - bool | None, - Field( - description='Whether the host page must initiate the polite-load phase. IAB-compliant banners require true.' - ), - ] = None - max_animation_duration_ms: Annotated[ - int | None, - Field( - description='Maximum total animation duration in milliseconds. IAB standard: 30000 (30 seconds).', - ge=0, - ), - ] = None - max_cpu_load_percent: Annotated[ - int | None, Field(description='Maximum CPU load percentage during render.', ge=1, le=100) - ] = None - mraid_required: Annotated[ - bool | None, Field(description='Whether MRAID compatibility is required (mobile in-app).') - ] = None - mraid_version: Annotated[ - MraidVersion | None, - Field(description='Required MRAID version when mraid_required is true.'), - ] = None - om_sdk_required: Annotated[ - bool | None, Field(description='Whether IAB Open Measurement SDK integration is required.') - ] = None - clicktag_macro: Annotated[ - ClicktagMacro | None, Field(description='Name of the click-tag macro the bundle must use.') - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the zip for non-HTML5 environments.' - ), - ] = None - backup_image_max_size_kb: Annotated[ - int | None, Field(description='Maximum backup image file size in kilobytes.', ge=1) - ] = None - ssl_required: bool | None = None - - -class FormatOptions100(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['html5'] = 'html5' - params: Annotated[ - Params98, - Field( - description="Interactive HTML5 banner delivered as a zip archive. Slot: `html5_bundle` (zip asset). Tracking model: MRAID + IAB Open Measurement (OM-SDK) + click-tag macro substitution + backup image fallback. Receivers unpack the zip, validate internal structure, and serve from CDN. Distinct from `image` (static, non-interactive) and `display_tag` (third-party served). The zip's entry point is typically `index.html`; click handling uses `clickTag` (or `clickTAG`) macro substitution.", - title='Canonical Format: HTML5 Banner', - ), - ] - - -class Slot99(Slot): - pass - - -class Params99(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot99] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection98] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - width: Annotated[ - int | None, - Field( - description='Required tag rendering width in pixels — use for fixed-size slots. For multi-size flexible slots use `sizes[]`; for responsive use `min_width`/`max_width`/`min_height`/`max_height`. Exactly one of `(width, height)`, `sizes[]`, or `min/max_width` + `min/max_height` ranges MUST be set.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Required tag rendering height in pixels. See `width` for size-mode mutual exclusion.', - ge=1, - ), - ] = None - sizes: Annotated[ - list[Size] | None, - Field( - description="List of accepted (width, height) pairs for a multi-size flexible slot. The buyer's third-party tag must render at one of the listed sizes; the seller picks which size to request at impression time. Mutually exclusive with `(width, height)` and with responsive ranges.", - min_length=1, - ), - ] = None - min_width: Annotated[ - int | None, - Field( - description='Minimum accepted width for responsive third-party tags. Pair with `max_width`. Mutually exclusive with `(width, height)` and `sizes[]`.', - ge=1, - ), - ] = None - max_width: Annotated[ - int | None, - Field( - description='Maximum accepted width for responsive third-party tags. Pair with `min_width`.', - ge=1, - ), - ] = None - min_height: Annotated[ - int | None, - Field( - description='Minimum accepted height for responsive third-party tags. Pair with `max_height`.', - ge=1, - ), - ] = None - max_height: Annotated[ - int | None, - Field( - description='Maximum accepted height for responsive third-party tags. Pair with `min_height`.', - ge=1, - ), - ] = None - supported_tag_types: Annotated[ - list[SupportedTagType] | None, Field(description='Tag delivery mechanisms accepted.') - ] = None - ssl_required: Annotated[ - bool | None, Field(description='Whether the tag URL must be HTTPS.') - ] = None - max_redirect_depth: Annotated[ - int | None, Field(description='Maximum redirect chain depth permitted.', ge=0) - ] = None - max_response_time_ms: Annotated[ - int | None, Field(description='Maximum tag-server response time in milliseconds.', ge=1) - ] = None - backup_image_required: Annotated[ - bool | None, - Field( - description='Whether a backup image must accompany the tag for environments that cannot render the third-party tag.' - ), - ] = None - backup_image_max_size_kb: Annotated[int | None, Field(ge=1)] = None - om_sdk_required: Annotated[ - bool | None, - Field( - description="Whether the buyer's tag must integrate IAB Open Measurement SDK for viewability." - ), - ] = None - - -class FormatOptions101(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['display_tag'] = 'display_tag' - params: Annotated[ - Params99, - Field( - description="Third-party-served display tag (JS, iframe, or 1×1 redirect). The buyer's adserver hosts the creative; the seller calls the tag URL at impression time. Slot: `tag_url` (url asset with appropriate `url_type`). Tracking model: opaque to seller — third party serves and measures. Click tracking via redirect URL substitution using universal_macros. Distinct from `image` (static asset hosted by seller) and `html5` (zip bundle hosted by seller).", - title='Canonical Format: Display Tag', - ), - ] - - -class Slot100(Slot): - pass - - -class Params100(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot100] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection99] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - card_aspect_ratio: Annotated[ - str | None, - Field( - description="Aspect ratio shared across all cards (e.g., '1:1', '1.91:1', '4:5').", - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_cards: Annotated[ - int | None, Field(description='Minimum card count (typical: 2 or 3).', ge=2) - ] = None - max_cards: Annotated[ - int | None, - Field(description='Maximum card count (typical: 6, 10, or 35 depending on platform).'), - ] = None - allowed_card_media_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='Asset types each card\'s `media` field may carry. Default: [\'image\']. Polymorphic carousels (Meta) allow [\'image\', \'video\']. Renamed from `allowed_card_asset_types` to disambiguate that this constrains the card\'s media payload, not the card-asset itself (which is always asset_type: "card").' - ), - ] = None - allowed_card_asset_types: Annotated[ - list[AllowedCardMediaAssetType] | None, - Field( - description='DEPRECATED — alias for `allowed_card_media_asset_types`. Kept for back-compat; prefer the new field name. Removed in 5.0.' - ), - ] = None - card_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - card_video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[ - int | None, Field(description='Maximum length of the carousel-level primary text.', ge=1) - ] = None - card_headline_max_chars: Annotated[ - int | None, - Field( - description='Per-card headline character limit. Governs the `headline` field on each card-asset in the `cards` slot.', - ge=1, - ), - ] = None - card_description_max_chars: Annotated[ - int | None, - Field( - description='Per-card description character limit. Governs the `description` field on each card-asset in the `cards` slot. Distinct from `card_headline_max_chars`: description is longer body copy (typically 100-500 chars); headline is the short label (typically 25-40 chars).', - ge=1, - ), - ] = None - ssl_required: bool | None = None - - -class FormatOptions102(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['image_carousel'] = 'image_carousel' - params: Annotated[ - Params100, - Field( - description='Multi-card swipeable carousel. The buyer ships a `cards` slot whose value is an **array** of [card-asset](/schemas/core/assets/card-asset.json) objects (a single key with an array value — NOT one key per card, NOT dotted/bracketed paths). Each card-asset carries: `asset_type: "card"`, `media` (an image or video asset), optional `headline` (text), optional `landing_page_url` (url asset). Per-card structure is the same across all cards; mixed orientations not allowed within a single carousel. Tracking model: per-card impression and engagement pixels + carousel-level engagement (swipe, view-time). Allowed asset types for a card\'s `media` field: `image` and `video` (Meta-style mixed-media); platforms can narrow to image-only or video-only via `allowed_card_media_asset_types`.\n\nThe manifest\'s `assets.cards` value is an array of card-asset objects. Example: `"cards": [{"asset_type": "card", "media": {"asset_type": "image", "url": "..."}, "headline": "Buy now", "landing_page_url": {"asset_type": "url", "url_type": "clickthrough", "url": "..."}}, ...]`. Each card-asset validates against the card schema; per-card platform extensions attach via the card\'s `platform_extensions` field, never via inline non-canonical keys.', - title='Canonical Format: Image Carousel', - ), - ] - - -class Slot101(Slot): - pass - - -class Params101(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot101] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection100] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Annotated[ - Orientation | None, - Field( - description='Video orientation. Vertical = 9:16 (Reels, Stories, Shorts). Horizontal = 16:9 (instream, CTV). Square = 1:1 (in-feed).' - ), - ] = None - aspect_ratio: Annotated[ - str | None, - Field( - description='Aspect ratio. Inferred from orientation if omitted.', - pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$', - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship (see `duration_ms_range` description).', - ge=1, - ), - ] = None - video_codecs: list[VideoCodec] | None = None - audio_codecs: list[AudioCodec] | None = None - containers: list[Container] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_file_size_mb: Annotated[int | None, Field(ge=1)] = None - frame_rates: list[float] | None = None - captions: Captions | None = None - om_sdk_required: bool | None = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - primary_text_max_chars: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - cta_values: list[str] | None = None - companion_banner_widths: Annotated[ - list[CompanionBannerWidth] | None, - Field(description='Permitted companion banner widths (instream video).'), - ] = None - companion_banner_heights: list[CompanionBannerHeight] | None = None - asset_source: Annotated[ - AssetSource | None, - Field( - description='Where the rendered asset bytes come from. Single shared enum across canonicals. See `image.json#asset_source` for the full semantics. `publisher_host_recorded` is audio-specific and has no defined behavior on video. `publisher_owned_reference` is valid when the product accepts an existing post reference via a `published_post` slot instead of uploaded video bytes. Adopters MUST select a value appropriate to the canonical.' - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded video. When `rejected`, the buyer cannot ship a video asset directly — they must use build_creative, sync_creatives with brief inputs, or sync_creatives with an accepted reference asset so the seller produces or resolves the video.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions103(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_hosted'] = 'video_hosted' - params: Annotated[ - Params101, - Field( - description='Direct video file (mp4/webm/mov) hosted by the buyer. Slot: `video_main` (video asset, file or hosted URL), optional `headline`, `brand_name`, `cta`, `companion_banner`, `landing_page_url`. Tracking model: IAB Open Measurement SDK + external impression/click/quartile pixels via universal_macros. Orientation is a parameter (vertical 9:16 / horizontal 16:9 / square 1:1); slot shape includes optional `brand_name` (typical for vertical short-form) and optional `companion_banner` (typical for horizontal instream). Distinct from `video_vast` (VAST tag, inherent VAST event tracking) — receivers fire impression and click pixels at delivery time.', - title='Canonical Format: Hosted Video', - ), - ] - - -class Slot102(Slot): - pass - - -class Params102(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot102] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection101] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - orientation: Orientation | None = None - aspect_ratio: Annotated[ - str | None, Field(pattern='^[0-9]+(\\.[0-9]+)?:[0-9]+(\\.[0-9]+)?$') - ] = None - vast_version: Annotated[VastVersion | None, Field(description='Required VAST version.')] = None - vpaid_enabled: Annotated[ - bool | None, - Field( - description='Whether VPAID interactivity is supported. When true, the VAST tag may carry VPAID JS/Flash payloads.' - ), - ] = None - vpaid_version: VpaidVersion | None = None - simid_supported: Annotated[ - bool | None, - Field(description='Whether IAB SIMID interactive video extensions are supported.'), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - min_width: Annotated[int | None, Field(ge=1)] = None - max_width: Annotated[int | None, Field(ge=1)] = None - min_height: Annotated[int | None, Field(ge=1)] = None - max_height: Annotated[int | None, Field(ge=1)] = None - linear_required: Annotated[ - bool | None, - Field(description='Whether the VAST creative must be linear (non-skippable in-stream).'), - ] = None - skippable_after_ms: Annotated[ - int | None, - Field( - description='When skippable, the buyer-side skip threshold in milliseconds (e.g., 5000 for 5-second skippable pre-roll).', - ge=0, - ), - ] = None - max_wrapper_depth: Annotated[ - int | None, Field(description='Maximum VAST wrapper redirect depth permitted.', ge=0) - ] = None - ssl_required: bool | None = None - - -class FormatOptions104(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['video_vast'] = 'video_vast' - params: Annotated[ - Params102, - Field( - description='VAST-tag-delivered video creative. Slot: `vast_tag` (vast asset, URL or inline XML, VAST 2.x-4.x). Tracking model: VAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `expand`, `collapse`, `fullscreen`, `creativeView`, `clickTracking`, `error`. VPAID interactivity via `vpaid_enabled: true` flag. SIMID extensions for interactive video supported as VAST extensions. Orientation is a parameter (vertical / horizontal / square). Distinct from `video_hosted` (direct file with external tracking).', - title='Canonical Format: VAST Video', - ), - ] - - -class Slot103(Slot): - pass - - -class Params103(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot103] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection102] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - duration_ms_range: Annotated[ - list[DurationMsRange | None] | None, - Field( - description='[min, max] duration in milliseconds. Either endpoint MAY be null to express an unbounded side: [null, 60000] means up to 60s; [15000, null] means at least 15s. [null, null] is invalid because at least one endpoint must be bounded. **Precedence**: when both `duration_ms_exact` and `duration_ms_range` ship on the same product, `duration_ms_exact` takes precedence — buyers MUST validate against the exact value and ignore the range. SDKs SHOULD lint a warning when both fields ship; producers SHOULD pick one.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - audio_codecs: list[AudioCodec4] | None = None - audio_sample_rates: list[AudioSampleRate] | None = None - audio_channels: list[AudioChannel] | None = None - min_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - max_bitrate_kbps: Annotated[int | None, Field(ge=1)] = None - loudness_lufs: Annotated[ - float | None, - Field( - description='Required integrated loudness in LUFS (typical: -16 for streaming/podcast, -23 for broadcast). Negative values.' - ), - ] = None - loudness_tolerance_db: Annotated[ - float | None, Field(description='Permitted deviation from loudness_lufs in dB.', ge=0.0) - ] = None - true_peak_dbfs: Annotated[ - float | None, Field(description='Maximum true-peak level in dBFS (typical: -2).') - ] = None - asset_source: Annotated[ - AssetSource | None, - Field( - description="Where the rendered audio bytes come from. Single shared enum across canonicals (see `image.json#asset_source` for the full semantics). `publisher_host_recorded`: the publisher's host records the audio (podcast host-read pattern); buyer must use the publisher's build_creative capability. `publisher_owned_reference` is valid only when the product accepts a reference asset whose publisher-owned source resolves to playable audio. `publisher_host_recorded` remains the normal audio-specific host-read value." - ), - ] = AssetSource.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description="Whether the product accepts buyer-uploaded audio. When `rejected`, the buyer cannot ship an audio asset directly — they must use build_creative (or sync_creatives with brief inputs) so the seller produces the audio. Combined with `asset_source`, lets a product declare 'I produce audio from briefs and refuse buyer uploads' (asset_source=`seller_pre_rendered_from_brief`, buyer_asset_acceptance=`rejected`)." - ), - ] = BuyerAssetAcceptance.accepted - companion_image_required: bool | None = None - companion_image_aspect_ratio: str | None = None - companion_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - brand_name_max_chars: Annotated[int | None, Field(ge=1)] = None - - -class FormatOptions105(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_hosted'] = 'audio_hosted' - params: Annotated[ - Params103, - Field( - description="Direct audio creative — buyer ships an `audio` asset (mp3/aac/wav) for asset-driven products, or ships a `script` / `creative_brief` text asset for products where the seller produces audio internally (podcast host-reads, TTS synthesis). Optional companion slots: `companion_image`, `brand_name`, `landing_page_url`. Tracking model: standard impression + completion + companion-image-click pixels via universal_macros. Distinct from `audio_daast` (DAAST tag, inherent DAAST event tracking). For host-reads and synthesized audio, the format declares `asset_source: 'publisher_host_recorded'` or `'agent_synthesized'` plus `buyer_asset_acceptance: 'rejected'`; the format's `slots` declaration enumerates which assets the buyer ships (e.g., `script` text asset for host-reads). The seller decides how to consume each asset (render verbatim vs produce audio from text) — there is no separate manifest 'inputs' map; everything the buyer ships goes in `assets`.", - title='Canonical Format: Hosted Audio', - ), - ] - - -class Slot104(Slot): - pass - - -class Params104(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot104] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection103] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - daast_version: DaastVersion | None = None - duration_ms_range: Annotated[ - list[DurationMsRangeItem] | None, - Field( - description='[min, max] duration in milliseconds. **Precedence**: `duration_ms_exact` takes precedence when both ship. SDKs SHOULD lint a warning when both fields ship.', - max_length=2, - min_length=2, - ), - ] = None - duration_ms_exact: Annotated[ - int | None, - Field( - description='When set, duration must equal exactly this value. Takes precedence over `duration_ms_range` when both ship.', - ge=1, - ), - ] = None - linear_required: bool | None = None - max_wrapper_depth: Annotated[int | None, Field(ge=0)] = None - ssl_required: bool | None = None - companion_image_required: bool | None = None - - -class FormatOptions106(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['audio_daast'] = 'audio_daast' - params: Annotated[ - Params104, - Field( - description='DAAST-tag-delivered audio creative (audio analog of VAST). Slot: `daast_tag` (daast asset, URL or inline XML). Tracking model: DAAST events inherent to the spec — `impression`, `firstQuartile`, `midpoint`, `thirdQuartile`, `complete`, `start`, `pause`, `resume`, `mute`, `unmute`, `clickTracking`, `error`. Distinct from `audio_hosted` (direct file with external tracking).', - title='Canonical Format: DAAST Audio', - ), - ] - - -class Slot105(Slot): - pass - - -class Params105(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot105] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection104] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - supported_catalog_types: Annotated[ - list[SupportedCatalogType] | None, Field(description='Catalog types this product accepts.') - ] = None - min_items: Annotated[ - int | None, Field(description='Minimum catalog item count buyer must supply.', ge=1) - ] = None - max_items: Annotated[ - int | None, Field(description='Maximum items considered for placement.') - ] = None - fanout_mode: Annotated[ - FanoutMode | None, - Field( - description='How items map to delivery: per_item = one ad per catalog item; multi_item_in_creative = composed multi-item ad (Pinterest Collection, Snap Collection); single_item = one ad showing one item.' - ), - ] = None - required_catalog_fields: Annotated[ - list[str] | None, - Field( - description="Catalog item fields the seller requires (e.g., ['title', 'image_url', 'price'])." - ), - ] = None - supported_id_types: Annotated[ - list[SupportedIdType] | None, - Field(description='Catalog identifier types the placement renders against.'), - ] = None - hero_asset_supported: Annotated[ - bool | None, - Field( - description='Whether the buyer can supply a hero/banner asset alongside the catalog (Pinterest Collection pattern).' - ), - ] = None - item_production_model: Annotated[ - ItemProductionModel | None, - Field( - description='How each per-item creative is produced. Covers the same production-source axis as `asset_source` on `image` / `video_hosted` / `audio_hosted` but with a 4-value subset — drops `publisher_host_recorded` because it\'s audio-specific and doesn\'t apply to retail-media catalog placements. SDK codegen MAY share a base enum and narrow per-canonical, or emit two distinct enums; either way the wire values overlap exactly for the 4 retained values. `buyer_uploaded` (default, current Amazon/Criteo/CitrusAd pattern): the buyer\'s catalog already contains rendered assets per item; the seller composes the placement using those assets. ("Uploaded" reads slightly off for catalog-keyed items where the buyer didn\'t actively upload bytes — the catalog ingestion already supplied them — but the semantic is the same: rendered bytes are buyer-supplied, not seller-produced.) `seller_pre_rendered_from_brief`: the buyer ships a brief plus the catalog reference; the seller renders one creative per catalog item from the brief at sync_creatives time. `seller_human_designed`: seller\'s design team produces per-item renders manually. `agent_synthesized`: AI synthesis pipeline produces per-item renders; pair with `synthesis_nondeterministic: true` for Veo/Sora-class generative video applied per item. Captures the multi-output generative pattern (1 brief × N catalog items → N rendered creatives) under the existing canonical without requiring a separate canonical. Distinct from `fanout_mode`, which describes how items map to delivery slots after rendering.' - ), - ] = ItemProductionModel.buyer_uploaded - - -class FormatOptions107(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['sponsored_placement'] = 'sponsored_placement' - params: Annotated[ - Params105, - Field( - description="Catalog-driven retail-media format. Slot: `source_catalog` (catalog asset — product/SKU/ASIN/GTIN catalog reference, REQUIRED), optional `hero_asset`, optional `landing_page_url`. Buyer supplies the catalog reference; surface composes per-item or multi-item rendering using its native placement template. **Composition is deterministic** — buyer can predict per-slot rendering from the catalog item structure. Tracking model: per-item impression + click + conversion (catalog-keyed via offering_id/sku/gtin macros). Covers Amazon Sponsored Products, Criteo Sponsored Products, CitrusAd Sponsored Products, Walmart Connect Sponsored Products, Pinterest Collection (catalog-driven mode).\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for catalog-driven retail-media placements ONLY. The defining feature is the `source_catalog` slot — products under this canonical compose their creative *per catalog item* using the buyer-supplied catalog feed. Without a catalog feed there is nothing to render against. Buyer agents reading `format_kind: sponsored_placement` MUST attach a catalog reference; sellers MUST require `source_catalog` in the manifest.\n\n**Not this canonical (route elsewhere):**\n- IAB in-feed native ads, content-recommendation widgets (Taboola, Outbrain, Yahoo Native, AdMob Native, in-feed sponsored cards) — use `native_in_feed` (asset-bundle composition; no catalog).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Single-image or single-video creative — use `image` or `video_hosted`.\n\nThe earlier broader framing ('any sponsored placement') was too loose for buyer-agent routing — a buyer reading `sponsored_placement` couldn't disambiguate a catalog-driven Amazon SP from an in-feed Taboola widget. As of 3.1, the canonical is narrowed to catalog-keyed retail-media; native moves to `native_in_feed`. Distinct from `responsive_creative` (algorithmic combinator from buyer pool) and `agent_placement` (text/audio AI-surface composition).", - title='Canonical Format: Sponsored Placement (retail-media catalog-driven)', - ), - ] - - -class Slot106(Slot): - pass - - -class Params106(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot106] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection105] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - title_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the title slot. IAB native typical: 25 (short) to 90 (long). Buyer agents SHOULD validate ship-time title length against this.', - ge=1, - ), - ] = None - body_text_max_chars: Annotated[ - int | None, - Field( - description='Maximum character length for the body_text slot. IAB native typical: 90 (mainline) to 140 (extended).', - ge=1, - ), - ] = None - cta_max_chars: Annotated[ - int | None, - Field(description='Maximum character length for the cta slot. Typical: 15–25.', ge=1), - ] = None - cta_values: Annotated[ - list[str] | None, - Field( - description="Permitted CTA values for this product (e.g., ['LEARN_MORE', 'SHOP_NOW', 'SIGN_UP', 'DOWNLOAD']). When set, narrows the cta slot to a closed enum." - ), - ] = None - main_image_sizes: Annotated[ - list[MainImageSize] | None, - Field( - description='Accepted (width, height) pairs for the main_image slot. Common IAB native sizes: 1200×627 (1.91:1), 1080×1080 (1:1), 1080×1350 (4:5).', - min_length=1, - ), - ] = None - icon_size: Annotated[ - IconSize | None, - Field( - description='Required (width, height) for the icon slot when present (typical: 80×80 or 100×100).' - ), - ] = None - max_image_file_size_kb: Annotated[ - int | None, - Field(description='Maximum file size in kilobytes for main_image and icon.', ge=1), - ] = None - image_formats: Annotated[ - list[ImageFormat3] | None, Field(description='Permitted image file formats.') - ] = None - ssl_required: Annotated[ - bool | None, - Field( - description='Whether trackers, landing pages, and image URLs must be served over HTTPS.' - ), - ] = None - asset_source: Annotated[ - AssetSource9 | None, - Field( - description="Where the rendered native assets come from. `publisher_host_recorded` is omitted (audio-specific and not meaningful for native). Other values mirror the shared production-source axis used on `image` / `video_hosted`. `buyer_uploaded` (default): buyer ships pre-rendered title/image/body. `seller_pre_rendered_from_brief`: buyer ships a brief, seller renders the native bundle. `agent_synthesized`: AI synthesis pipeline produces title + image + body from a brief; pair with `synthesis_nondeterministic: true` for generative pipelines that can't guarantee in-spec output. `publisher_owned_reference`: buyer ships an existing published post reference; the seller resolves the post into the native presentation after authorization/review." - ), - ] = AssetSource9.buyer_uploaded - buyer_asset_acceptance: Annotated[ - BuyerAssetAcceptance | None, - Field( - description='Whether the product accepts buyer-uploaded native assets. When `rejected`, the buyer cannot ship pre-rendered title/image/body — they must use `build_creative`, `sync_creatives` with brief inputs, or an accepted `published_post` reference so the seller produces or resolves the native bundle.' - ), - ] = BuyerAssetAcceptance.accepted - - -class FormatOptions108(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['native_in_feed'] = 'native_in_feed' - params: Annotated[ - Params106, - Field( - description='IAB-shaped native creative for in-feed and content-recommendation surfaces. Default slots cover the primary IAB OpenRTB Native 1.2 asset types — `title` (Title Asset), `body_text` (Data Asset type 2), `main_image` (Image Asset main), `icon` (Image Asset icon), `cta` (Data Asset type 12), `advertiser_name` (Data Asset type 1), `sponsored_label` (Title-adjacent), `landing_page_url` (Link Asset), `display_url` (Data Asset type 11 — visible URL/domain, distinct from clickthrough), `rating` (Data Asset type 3 — app/product rating), `price` (Data Asset type 6 — product price), plus renderer-fired `impression_tracker` / `viewability_tracker` / `click_tracker` (`pixel_tracker`). Products MAY use `slots_override` to add other IAB Native data asset types (likes — type 4, downloads — type 5, saleprice — type 7, phone_number — type 8, address — type 9, desc2 — type 10, etc.) or to remove slots the surface doesn\'t render. The publisher\'s renderer assembles these into its own look-and-feel — feed card, content-recommendation slot, in-stream native unit. Buyer ships a single asset bundle; the surface chooses presentation.\n\n**Scope (normative — buyer-agent routing).** This canonical is the home for:\n- IAB OpenRTB Native 1.2 in-feed native ads (publisher feeds, app feeds)\n- Content-recommendation widgets (Taboola, Outbrain, Yahoo Recommendations)\n- AdMob Native / Yahoo Native publisher slots\n- In-feed sponsored placements without catalog dependency\n\n**Not this canonical:**\n- Catalog-driven retail-media (Amazon SP, Criteo SP, CitrusAd SP) — use `sponsored_placement` (requires `source_catalog`).\n- Algorithmic surface that picks from a buyer-supplied asset pool (Google PMax, Meta Advantage+) — use `responsive_creative`.\n- Multi-card carousel — use `image_carousel`.\n- Video-first native units where the asset is a hosted video file — use `video_hosted` with `applies_to_channels: ["native"]`.\n\nDistinct from `sponsored_placement` along the catalog axis: native_in_feed is asset-bundle composition; sponsored_placement is catalog-row composition. A buyer agent reading `format_kind: native_in_feed` knows to assemble title + image + body + CTA; reading `format_kind: sponsored_placement` knows to attach a catalog feed.', - title='Canonical Format: Native In-Feed', - ), - ] - - -class Slot107(Slot): - pass - - -class Params107(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot107] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection106] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - headlines_min: Annotated[int | None, Field(ge=0)] = None - headlines_max: Annotated[int | None, Field(ge=0)] = None - headline_max_chars: Annotated[int | None, Field(ge=1)] = None - long_headlines_min: Annotated[int | None, Field(ge=0)] = None - long_headlines_max: Annotated[int | None, Field(ge=0)] = None - long_headline_max_chars: Annotated[int | None, Field(ge=1)] = None - descriptions_min: Annotated[int | None, Field(ge=0)] = None - descriptions_max: Annotated[int | None, Field(ge=0)] = None - description_max_chars: Annotated[int | None, Field(ge=1)] = None - images_landscape_min: Annotated[int | None, Field(ge=0)] = None - images_landscape_max: Annotated[int | None, Field(ge=0)] = None - images_landscape_aspect_ratio: str | None = None - images_square_min: Annotated[int | None, Field(ge=0)] = None - images_square_max: Annotated[int | None, Field(ge=0)] = None - images_vertical_min: Annotated[int | None, Field(ge=0)] = None - images_vertical_max: Annotated[int | None, Field(ge=0)] = None - videos_min: Annotated[int | None, Field(ge=0)] = None - videos_max: Annotated[int | None, Field(ge=0)] = None - video_min_duration_ms: Annotated[int | None, Field(ge=1)] = None - video_max_duration_ms: Annotated[int | None, Field(ge=1)] = None - logo_min: Annotated[int | None, Field(ge=0)] = None - logo_max: Annotated[int | None, Field(ge=0)] = None - logo_aspect_ratios: list[str] | None = None - business_name_max_chars: Annotated[int | None, Field(ge=1)] = None - asset_image_max_file_size_kb: Annotated[int | None, Field(ge=1)] = None - supports_catalog_input: Annotated[ - bool | None, - Field( - description='Whether the product can additionally consume a catalog reference (e.g., PMax with product feed).' - ), - ] = None - - -class FormatOptions109(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['responsive_creative'] = 'responsive_creative' - params: Annotated[ - Params107, - Field( - description='Buyer supplies a pool of typed assets (multiple headlines, descriptions, images, videos, logos); the surface algorithmically composes combinations per placement. **Composition is algorithmic** — surface picks combinations and reports per-asset performance breakdowns. Covers Google Responsive Display Ads (RDA), Responsive Search Ads (RSA), Performance Max (PMax), Demand Gen, and Meta Advantage+ creative. Industry term: "Responsive" (Google) / "Advantage+ creative" (Meta) / "Dynamic Creative" (older Meta term). Distinct from `sponsored_placement` (catalog-driven, deterministic) and `agent_placement` (AI-surface composition). The structured `slots` field below enumerates expected canonical asset_group_id slots; per-slot count/length narrowing lives in flat parameters (`headlines_min`, `headline_max_chars`, etc.).', - title='Canonical Format: Responsive Creative', - ), - ] - - -class Slot108(Slot): - pass - - -class Params108(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - experimental: Annotated[ - bool | None, - Field( - description='When true, this canonical (or a seller\'s specific narrowing of it) may not work as declared — adopters SHOULD have a v1 fallback ready and SHOULD NOT route production budget without testing. Same semantics as `experimental` on protocols: \'this is shipping but may break, evolve, or fail.\' Buyers reading `experimental: true` SHOULD prefer the v1 path when a v1 fallback exists for the same product (via `format_ids` on the parent product or via the v2 declaration\'s `v1_format_ref`).\n\nThree drivers of `experimental: true`:\n1. **Spec maturity** — the canonical\'s tracking model or parameter shape is still being settled (`agent_placement`\'s tracking macros, `sponsored_placement`\'s per-adapter contracts, `responsive_creative`\'s algorithmic composition).\n2. **Adopter runtime gap** — the seller has declared the canonical in their catalog but their runtime doesn\'t yet honor it cleanly.\n3. **Custom shapes** — `format_kind: "custom"` is inherently experimental until the working group promotes a `format_shape` to a first-class canonical.\n\nReplaces the earlier `status` enum (`stable | preview | deprecated`) + `runtime_status` enum (`stable | preview | declared_only`) — two axes with subtle overlap. The single boolean is what buyers actually care about: do I treat this as production-stable or as \'try at my own risk.\' Sellers SHOULD set `experimental: true` on canonicals or product declarations that aren\'t yet production-ready, regardless of which axis (spec, runtime, custom) drives the experimentation. The 9 non-experimental canonicals at 3.1 GA (`image`, `html5`, `display_tag`, `image_carousel`, `video_hosted`, `video_vast`, `audio_hosted`, `audio_daast`, `native_in_feed`) default to non-experimental at the canonical level; sellers MAY still mark a specific product declaration experimental (e.g., a beta runtime path for an existing product).' - ), - ] = False - deprecated: Annotated[ - bool | None, - Field( - description="When true, this canonical (or a seller's specific narrowing of it) is going away. Existing adopters are supported through the deprecation cycle; new adoption is discouraged. Pair with `migration_target_version` to indicate when the canonical is expected to be removed. Distinct from `experimental`: an experimental canonical may stabilize and stop being experimental; a deprecated canonical is on a sunset path." - ), - ] = False - v1_translatable: Annotated[ - bool | None, - Field( - description="Whether this canonical has any v1 named-format equivalent. `true` (default) — the canonical is structurally expressible as one or more v1 named formats (IAB display sizes, VAST tags, DAAST tags, etc.); v1→v2 projection via `v1-canonical-mapping.json` is meaningful. `false` — the canonical is inherently new in v2 and has no v1 form; v1's `list_creative_formats` couldn't express it because the underlying concept (algorithmic surface composition, AI-surface mentions, retail-media catalog placements, multi-card carousels) didn't exist as a v1 named-format archetype.\n\nLets SDKs distinguish two failure modes that today look identical: (a) the registry hasn't covered this canonical yet (correctable — seller adds explicit `canonical` field or files a registry entry) vs (b) no v1 path is possible (informational — buyer needs v2-aware consumption, or seller declares `canonical_formats_only: true` on the product declaration). SDKs encountering `v1_translatable: false` on a canonical SHOULD NOT emit `FORMAT_PROJECTION_FAILED` (which signals registry-coverage gap) — instead surface the inherent v1-unreachability as a different diagnostic or skip silently. The 4 inherently-v2 canonicals at 3.1 GA: `image_carousel`, `sponsored_placement`, `responsive_creative`, `agent_placement`." - ), - ] = True - since_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version that introduced this canonical (e.g., '3.1', '3.2'). Lets adopters reason about minimum protocol version requirements when consuming a format declaration. Patch precision is intentionally rejected — canonicals are introduced at minor-version boundaries.", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - migration_target_version: Annotated[ - str | None, - Field( - description="AdCP MAJOR.MINOR version by which the working group expects this canonical to stabilize, surface a breaking revision, or (when `deprecated: true`) be removed. Patch precision is intentionally rejected — canonicals shift at minor-version boundaries. Absence signals 'no specific target' (omit the field rather than use a placeholder like 'unknown').", - pattern='^[1-9]\\d*\\.(0|[1-9]\\d*)$', - ), - ] = None - composition_model: Annotated[ - CompositionModel | None, - Field( - description='Whether the surface composes deterministically (buyer can predict per-slot rendering — sponsored_placement, image, video) or algorithmically (surface chooses combinations or phrasing — responsive_creative, agent_placement).' - ), - ] = None - provenance_required: Annotated[ - bool | None, - Field( - description='When true, the product rejects unsigned synthesized assets. Builders calling build_creative MUST attach a C2PA-compatible provenance manifest attributing synthesis to the creative agent.' - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Platform-specific extensions narrowing the canonical (pixel ID shapes, conversion event taxonomies, platform-specific CTAs/destinations). Each extension is a URI+digest reference resolved against the bundled `extensions` map in get_products responses or fetched directly.\n\n**Collision precedence (normative).** When two or more `platform_extensions[]` entries on the same declaration extend the same target (e.g., both extend `tracking`) with overlapping field names, **array order is authoritative — later entries override earlier ones on a per-field basis** (last-in-array-wins). SDKs MUST surface the overlap via the `errors[]` array on the `get_products` response with a structured code (`FORMAT_DECLARATION_DIVERGENT` is appropriate when the overlap appears across dual-emitted shapes; a producer-self-emitted overlap on a single declaration SHOULD use the same code with `error.details: { collision_kind: "platform_extension_field", target, overlapping_fields, winning_extension_uri }`). Producers SHOULD avoid the collision by emitting one extension per target or by partitioning fields across extensions; the deterministic precedence is for last-resort consistency across SDK implementations, not a sanctioned merging strategy.' - ), - ] = None - synthesis_nondeterministic: Annotated[ - bool | None, - Field( - description="When true, the format's production pipeline is genuinely nondeterministic — the platform cannot guarantee that synthesis from a given input set produces in-spec output. Veo / Sora / Runway-class generative video, and other AI-synthesis flows where output dimensions, duration, or quality vary per run. Implies a different validation contract: predictive `validate_input` is impossible; the platform's own post-synthesis QA loop applies; if the QA loop exhausts without producing a valid artifact, `build_creative` returns task_failed with a synthesis_failed reason. Distinct from `composition_model` (which describes how the surface composes per-slot rendering, not whether synthesis is deterministic). When false or absent, the format's production is predictable enough that `validate_input` can predict output properties from input properties.\n\n**Compatibility with `asset_source` / `item_production_model`**: `synthesis_nondeterministic: true` MAY pair with any of `seller_pre_rendered_from_brief`, `seller_human_designed`, or `agent_synthesized` (the QA loop is concept-level, not source-specific — 'seller renders from brief but each retry differs' is just as nondeterministic as Veo). It MUST NOT pair with `buyer_uploaded` (the buyer ships pre-rendered bytes; there's no synthesis step to be nondeterministic about). It MUST NOT pair with `publisher_host_recorded` (the publisher's host produces a deterministic-from-script output even if the human voice varies). When `synthesis_nondeterministic: true` is set with an incompatible source, validators SHOULD reject with a structured error." - ), - ] = False - slots: Annotated[ - list[Slot108] | None, - Field( - description="Programmatic declaration of which canonical asset_group_id slots a manifest targeting this format must (or may) populate. Lets SDK codegen and validators enumerate expected slots without parsing the format's prose description. Each entry references an asset_group_id from the canonical vocabulary registry, paired with an `asset_type` so the validator knows which asset schema to apply. Format-level narrowing parameters that apply across all slots (e.g., flat `headline_max_chars` on responsive_creative) may also live on the format declaration; per-slot constraints (a specific slot's `max_chars` or `max_size_kb`) live on the slot entry." - ), - ] = None - required_connections: Annotated[ - list[RequiredConnection107] | None, - Field( - description='Downstream platform connections or grants required to use this format declaration. These are in addition to the single AdCP caller credential. Use this when a platform product requires multiple downstream grants, such as an advertiser account connection plus a publisher identity or post authorization for published-post references.' - ), - ] = None - reference_mutability: Annotated[ - ReferenceMutability | None, - Field( - description='Policy for formats whose `slots` accept a `published_post` reference. `immutable_snapshot`: seller snapshots the referenced post at approval and later source changes do not change the served creative. `mutable_requires_reapproval`: the source post may change and material changes require review before continued serving. `mutable_auto_recheck`: the source post may change and the seller continuously or periodically rechecks authorization/policy without requiring buyer resubmission. Omit when the format has no `published_post` slot.' - ), - ] = None - production_window_business_days: Annotated[ - int | None, - Field( - description='Typical production turnaround in business days when the format requires seller-side production (e.g., host-recording from a buyer-supplied script). 0 for synchronous (e.g., generative AI); >0 for human-produced (e.g., podcast host-read). Absent when no production is required (buyer uploads complete creative).', - ge=0, - ), - ] = None - output_modality: Annotated[ - OutputModality | None, - Field( - description='How the surface presents the mention. `text` = inline text (chat, search snippet). `audio` = TTS-synthesized voice. `card` = structured card with optional image + text.' - ), - ] = None - max_mention_length_chars: Annotated[ - int | None, - Field( - description='For text output: maximum length of the surface-composed mention text.', - ge=1, - ), - ] = None - max_mention_duration_ms: Annotated[ - int | None, - Field( - description='For audio output: maximum duration of the spoken mention in milliseconds.', - ge=1, - ), - ] = None - supports_offering_reference: Annotated[ - bool | None, - Field( - description='Whether the product accepts an offering reference (specific product/service to promote within the mention) in addition to brand context.' - ), - ] = None - supports_landing_page_url: Annotated[ - bool | None, - Field( - description='Whether the surface attaches a landing page URL to the mention (citation, learn-more link).' - ), - ] = None - tone_constraints: Annotated[ - list[str] | None, - Field( - description="**Advisory only.** Buyer-declared brand-voice preferences the surface SHOULD honor (e.g., ['formal', 'no_superlatives']). LLM/agentic surfaces have no protocol-level mechanism to verify enforcement — adopters that need hard guarantees should rely on brand.json voice declarations and post-mention review rather than this field. Future revisions may tie this to a structured tone vocabulary; for now treat as free-text guidance." - ), - ] = None - disclosure_required: Annotated[ - bool | None, - Field( - description='Whether the surface must include an explicit sponsorship disclosure label.' - ), - ] = None - - -class FormatOptions110(AdCPBaseModel): - format_option_id: Annotated[ - str | None, - Field( - description="Stable identifier for this format declaration within its namespace. REQUIRED when the parent product's `format_options` contains multiple declarations sharing the same `format_kind` (so buyers can disambiguate which option a manifest targets via `manifest.format_option_ref`). SHOULD be set on EVERY `format_options[]` entry — not just when structurally required to break a `format_kind` collision — so V2-mental-model buyers can use the V2 authoring path (`PackageRequest.format_option_refs[]`, `creative-manifest.format_option_ref`) against the product. Publisher-catalog-backed options pair this with `publisher_domain`; product-local options omit `publisher_domain` and are selected by `format_option_id` within the target product. A product that ships without selectable `format_option_id` values on its `format_options[]` entries is structurally 3.1-conformant but is not V2-authorable: buyers fall back to v1 `format_ids[]` and lose the stable naming the V2 path was designed to provide. Sellers MUST reject V2 authoring against such products with `UNSUPPORTED_FEATURE` and `error.details.reason` set to `format_option_refs_not_published` per `package-request.json`. Format-internal (not a URI). Examples: 'display_image_300x250', 'responsive_search', 'daily_pulse_homepage_image'." - ), - ] = None - publisher_domain: Annotated[ - str | None, - Field( - description="Namespace for `format_option_id` when this declaration references or narrows a publisher-declared format option from that publisher's adagents.json top-level `formats[]`. Product-local options omit this field and are selected by `format_option_id` within the target product.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description="Optional seller-controlled human-readable label for this format declaration. Used by buyer dashboards, catalog UIs, and reporting surfaces to show a seller's own naming ('Homepage Takeover', 'Branded Canvas', 'Reels Premium Video') rather than the raw `format_kind` or `format_option_id`. Has no machine semantics — buyer agents route on `format_kind` and `format_option_id`; `display_name` is purely for human presentation. Freeform; no enumeration. Sellers SHOULD keep it stable once published to avoid dashboard churn." - ), - ] = None - applies_to_channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Optional subset of the parent product's `channels` to which this declaration applies. When omitted, the declaration applies to ALL channels declared on the product. Lets a multi-channel product (e.g., `channels: ['display', 'video']`) carry distinct format_options per channel — `format_options: [{format_kind: 'image', applies_to_channels: ['display']}, {format_kind: 'video_hosted', applies_to_channels: ['video']}]`. Buyers ship channel-appropriate manifests per `applies_to_channels`." - ), - ] = None - seller_preference: Annotated[ - SellerPreference | None, - Field( - description="Optional soft routing hint *within* a product's accepted set of formats — NOT an enforcement axis. `preferred` — seller actively recommends this format (often because of measurement, viewability, or render-quality differences); `accepted` — supported on equal footing with other format_options (default when omitted); `discouraged` — supported but suboptimal (e.g., legacy 3p-tag where the seller would prefer html5 for OM-SDK coverage). Buyer agents picking between format_options SHOULD respect seller preferences when their own constraints don't override.\n\n**Not an enforcement axis (normative).** `seller_preference` does NOT carry the meaning of 'this format won't work / required-only'. That case is structural: `format_options[]` IS the closed set of accepted formats; anything outside the list is rejected at `create_media_buy` regardless of preference. A seller that accepts only one format lists exactly that one entry — the structural fact does the enforcement work, no enum value needed. There is intentionally no `required` value; preference is bounded to *ranking within the already-accepted set*, not gating into it." - ), - ] = None - canonical_formats_only: Annotated[ - bool | None, - Field( - description='When true, this format declaration has no clean v1 projection and SDKs MUST NOT synthesize a v1 `format_id` for it. Buyers reading the product on the v1 wire path see this declaration absent from `format_ids`; only v2-aware buyers (reading `format_options`) discover it. Set explicitly for `format_kind: "custom"` declarations (no canonical exists in v1 to project onto) and for declarations whose canonical/parameter shape cannot round-trip through a v1 named format without semantic loss. The protocol does NOT mint synthetic v1 format_ids for unmappable declarations — the alternative (an `aao-synth/*` namespace populated automatically) was considered and rejected because adopters would index on synthetic IDs that have no stable identity. Producers SHOULD set `canonical_formats_only: true` rather than omit the declaration from `format_options` — explicit v2-only is more useful than silent absence.' - ), - ] = False - experimental: Annotated[ - bool | None, - Field( - description="When true, THIS seller's specific product declaration may not work as declared — even if the underlying canonical is stable. Use for beta runtime paths, forward-looking catalog entries the runtime doesn't yet honor, or experimental products where the seller wants buyer-side caution. Buyers reading `experimental: true` on a product declaration SHOULD prefer the legacy named-format path when a fallback exists for the same product (via `format_ids` on the parent product or via this declaration's `v1_format_ref`) and SHOULD validate via `validate_input` or a sandbox before routing production budget.\n\nIndependent of the canonical's own `experimental` flag — a stable canonical (e.g., `image`, `video_hosted`) can carry an experimental product declaration when the seller is shipping a new runtime path that isn't fully wired yet. Conversely, an experimental canonical (`sponsored_placement`, `responsive_creative`, `agent_placement`) MAY carry non-experimental product declarations where the seller's adopter contract is well-tested. Buyer SDKs SHOULD filter products with `experimental: true` from default views and offer an opt-in flag to surface them.\n\nReplaces the earlier `runtime_status` enum (`stable | preview | declared_only`) — same semantic ('use with caution') without the cognitive overhead of two stability axes." - ), - ] = False - format_shape: Annotated[ - str | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. Recognized global pattern this custom shape is an instance of, drawn from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json) (`multi_placement_takeover`, `roadblock`, `branded_content`, `cross_screen_sponsorship`, `sponsorship_lockup`, `newsletter_sponsorship`, `ar_lens`, `playable`, `live_event_sponsorship`, …). Non-canonical values valid (validators MAY soft-warn) — adopters CAN ship a shape that isn\'t yet in the registry. Adding entries is a vocabulary PR. Once a `format_shape` entry sees 2+ adopters with substantively similar `format_schema` content for 90+ days, the working group promotes it to a first-class canonical.' - ), - ] = None - v1_format_ref: Annotated[ - list[V1FormatRefItem] | None, - Field( - description="Authoritative v2 → v1 link, expressed as an array of one or more v1 `format_id` ({agent_url, id}) values. Each entry asserts that this canonical-formats declaration IS the same underlying format as the referenced v1 named format. Always an array (single-ref is `[{...}]`) so the multi-size case below has a clean wire shape — adopters surveyed in the SDK implementor review pushed for this over the lossy single-ref form.\n\nThe v2 declaration's `params` MUST narrow (be compatible with) each referenced v1 format's `requirements` — see the 'Narrows — formal definition' section in canonical-formats.mdx. SDKs comparing dual-emitted shapes (`Product.format_ids[]` ⊇ entries from `v1_format_ref` AND `Product.format_options[]` carrying this declaration) treat the link as the authoritative pairing and run the narrowing check between this declaration and EACH referenced v1 format file's `requirements`.\n\n**Multi-size fan-out (normative).** When the declaration carries `params.sizes: [{w,h}, ...]` (multi-size flexible slot), sellers SHOULD carry one `v1_format_ref[]` entry per size, each pointing at the per-size v1 named format in the AAO catalog. Example: a multi-size image declaration with `sizes: [300x250, 728x90, 970x250]` SHOULD carry `v1_format_ref: [{aao, display_300x250_image}, {aao, display_728x90_image}, {aao, display_970x250_image}]`. v1-only buyers then see the product on all three sizes via the `format_ids[]` dual-emission. When `v1_format_ref[]` count < `sizes[]` count, SDKs MUST emit `FORMAT_DECLARATION_V1_LOSSY_MULTI_SIZE` on the response `errors[]` (advisory, alongside the partial-coverage v1 emit — NOT in place of it). SDKs MAY (non-normative) fan out automatically by catalog lookup when `v1_format_ref[]` has length 1 and `sizes[]` has length N — opt-in, requires catalog access; sellers asserting refs is the source of truth.\n\nMutually exclusive with `canonical_formats_only: true` — a declaration can EITHER assert no v1 projection (`canonical_formats_only: true`) OR link to v1 named formats (`v1_format_ref[]`), never both. When neither is present, SDKs fall back to the resolution order in `v1-canonical-mapping.json` (seller's explicit `canonical` field on the v1 file → registry glob → structural match → fail-closed).\n\nThis is the v2-side authoritative replacement for the v1-side `canonical_parameters` field on `format.json` (which is deprecated for 3.1, removed at 4.0). Sellers SHOULD prefer authoring v2 declarations with `v1_format_ref[]` over mirroring the v2 shape onto v1 files via `canonical_parameters`; the directional link (v2 declaration → v1 identifiers) is the same fact without the parallel-shape drift surface.\n\n**AAO-hosted convention (normative).** For IAB-standard formats (image dimensions, VAST/DAAST tags, standard third-party tags, HTML5 banner bundles), sellers SHOULD point each `v1_format_ref[].agent_url` at the AAO-hosted canonical agent URL `https://creative.adcontextprotocol.org` and use the registry-published id (e.g., `display_300x250_image`, `video_vast_30s`, `audio_standard_30s`, `display_300x250_html`, `display_js`). This converges the v1-wire namespace: every seller's IAB MREC points at the same `{agent_url, id}` pair, so v1-only buyers' allowlists work uniformly. Without this convention, every publisher's 300x250 ships with a different `v1_format_ref` (theirs vs nytimes.example vs cnn.example vs …) and the v1 wire fragments into per-publisher namespaces — exactly what canonical-formats was designed to eliminate.\n\nFor platform-specific formats (Meta Reels, TikTok Spark, Snap Spotlight, etc.), each `v1_format_ref[].agent_url` SHOULD point at the platform's own agent_url when the platform has adopted AdCP and publishes its own `adagents.json` with `formats[]`. When the platform has NOT adopted AdCP, sellers SHOULD point at the AAO community-registry mirror — `https://creative.adcontextprotocol.org/translated/` + `id: ` (e.g., `https://creative.adcontextprotocol.org/translated/meta` + `id: meta_reels`). This keeps the v1 namespace converged across all sellers selling that platform's inventory until the platform owns its own adagents.json.\n\n**Platform-adoption cutover (normative).** When a platform adopts AdCP and publishes its own adagents.json, sellers MUST update `v1_format_ref[].agent_url` to the platform's adopted agent_url in the same minor release as the AAO mirror entry's `superseded_by` field goes live (see `static/schemas/source/adagents.json#superseded_by`). The AAO mirror entry SHOULD continue serving for ≥1 minor release after `superseded_by` is set, returning an advisory 'superseded' marker so v1 buyer allowlists keyed on the mirror URL get an explicit signal rather than a silent break. **Identity-confusion note**: the mirror URL is *format-shape namespace*, NOT seller identity. Inventory authorization always flows from `authorized_agents[]` + publisher signing keys; a buyer matching `v1_format_ref[].agent_url` against an allowlist is matching format-shape provenance, not seller identity.\n\n**Mirror domain migration (3.1).** Earlier drafts used `https://mirror.adcontextprotocol.org/translated/`. As of this release, the convention is `https://creative.adcontextprotocol.org/translated/` — sibling content under the AAO catalog domain we already host. Adopters who hardcoded the earlier mirror URL MUST migrate to the new path; the canonical-formats.mdx migration section documents the move. No transitional redirect is currently published (the earlier subdomain was never provisioned).\n\nFor seller-bespoke formats (a publisher's `acme_homepage_takeover` that doesn't fit IAB conventions), each `v1_format_ref[].agent_url` is the seller's own agent_url and the id is seller-namespaced. These won't appear in `v1-canonical-mapping.json`'s registry; they're seller-asserted only.", - min_length=1, - ), - ] = None - format_schema: Annotated[ - FormatSchema | None, - Field( - description='REQUIRED when `format_kind: "custom"`; otherwise MUST be absent. URI+digest reference to a fetchable schema describing this custom shape\'s actual `params` and `slots`. Same hosting model as `platform_extensions`: open-ecosystem publishers host the artifact at the canonical URI on their subdomain; closed-platform / walled-garden shapes resolve through the AAO mirror at `https://creative.adcontextprotocol.org/translated/...`. Buyer agents fetch by `uri@digest` (immutable per digest, aggressive caching, `Cache-Control: public, max-age=31536000, immutable`), validate `params` and `slots` against the fetched schema, and reason about manifests structurally — same mechanic as platform_extensions but at the format-structure level. Without `format_schema`, custom shapes would be opaque to buyer agents and the protocol would regress to per-seller integration code; that\'s why the schema is required, not optional.\n\n**Fetch contract (normative)** — `format_schema` is load-bearing for validation (unlike `platform_extensions`, which is informational on the *consumption* side). The *transport* rules below apply identically to BOTH fields — any SDK fetching a `platform-extension-ref.json` URI MUST apply this contract regardless of whether the field name is `format_schema` or `platform_extensions`. A shared SDK fetch path that drops to the weakest bar undermines `format_schema`\'s hardening. The consumption distinction (load-bearing vs informational) is about *what the body means*; the transport distinction is `https`-and-allowlisted regardless.\n\n- **Transport**: `https` only. Buyers MUST reject `http://`, `file://`, `data:`, and any non-`https` scheme. The URI MUST resolve to a JSON document that is itself a valid JSON Schema (Draft 07 or 2020-12; producers MUST declare `$schema`).\n- **SSRF protection**: buyers MUST resolve the URI hostname and reject if any resolved address is in RFC 1918 private space (`10.0.0.0/8`, `172.16.0.0/12`, `192.168.0.0/16`), loopback (`127.0.0.0/8`, `::1`), link-local (`169.254.0.0/16`, `fe80::/10`), CGNAT (`100.64.0.0/10`), or any RFC 6761 special-use name (`.local`, `.localhost`, `.internal`, `.test`, `.example`, `.invalid`). Cloud metadata endpoints (`169.254.169.254`, `metadata.google.internal`, `kubernetes.default.svc`) are explicitly forbidden — these are credential-leak primitives. Buyers MUST pin the connection to the resolved IP (or re-resolve and re-validate the allowlist per request) to defeat DNS rebinding.\n- **HTTP redirects**: MUST be disabled. If a follow is implemented at all, the redirect target MUST pass the same scheme + SSRF + allowlist checks; otherwise the fetch hard-fails. Open redirects on same-origin paths are otherwise a free SSRF primitive.\n- **Response size cap**: response body MUST be capped at 1 MiB. Enforce during streaming, not after full buffering. Over-cap hard-fails identically to digest mismatch.\n- **Timeout**: SDKs SHOULD apply a fetch timeout ≤5 seconds. Timeout SHOULD be treated identically to an HTTP 5xx response (transient — retry policy at the SDK\'s discretion; on persistent failure surface as unresolved and skip the declaration for this session).\n- **Digest verification**: SHA-256 of the response body MUST equal `digest`. **Digest mismatch is a hard fail** — the buyer MUST treat the format declaration as unresolvable and MUST NOT validate manifests against the mismatched body. A divergent digest is either a malicious substitution or producer error; either way, falling back to the un-verified body breaks the trust model. Digest format: `sha256:` prefix + 64 lowercase hex characters. Cache key is `uri@digest`; digest mismatch MUST NOT be cached as a negative result keyed on `uri` alone (defeats CDN-flap recovery), and MUST be distinguishable in telemetry from network 5xx / 404 (sustained mismatch is a substitution-attack signal, not a flap).\n- **Sandboxing of `$ref`**: fetched schemas MAY use `$ref`. Buyers MUST resolve `$ref` only to URIs that are (a) same-origin as the parent `format_schema.uri` after RFC 3986 §6 normalization (lowercase scheme + host, strip default port, normalize path dot-segments, no userinfo component), OR (b) hosted under the AAO catalog domain (`https://creative.adcontextprotocol.org/...`), OR (c) intra-document JSON Pointer refs (`#/...`) bounded to the parent document\'s parsed tree. Cross-origin `$ref` to arbitrary URIs MUST be rejected. `$ref: file://...` MUST be rejected unconditionally. Transitive `$ref` chains MUST be bounded at depth ≤8 AND `$ref` count ≤256 across the resolved tree (depth 8 with breadth 100 per level is 10^16 nodes — depth alone is not enough). Publishers SHOULD inline rather than $ref where possible.\n- **Schema-compile bounds (DoS protection)**: validators MUST bound CPU/memory on fetched schemas. Recommended: compiled-schema keyword count ≤10 000, `pattern` regexes evaluated with a non-backtracking engine (re2) OR under a per-pattern timeout, per-manifest validation budget ≤250 ms (exceeded budget → treat manifest as invalid, surface telemetry signal). Without these, a \'valid\' schema with catastrophic regex backtracking or exponential `allOf`/`anyOf` expansion pins a CPU forever.\n- **Cache**: buyers cache fetched schemas by `uri@digest` and treat them as immutable (the same hosting contract as `platform_extensions`). On `404`, network partition, or persistent fetch failure, buyers SHOULD degrade gracefully (treat the declaration as unresolved, skip it for the current `get_products` response, surface via `errors[]` with the relevant code) rather than failing the entire session.\n- **Schema-not-valid handling**: if the fetched body parses as JSON but is not a valid JSON Schema, the buyer MUST treat the declaration as unresolvable (same as digest mismatch) and surface via `errors[]`. Validators MUST NOT attempt partial validation against an invalid schema.\n- **AAO catalog trust**: `https://creative.adcontextprotocol.org/*` is a single trust anchor in the same-origin allowlist; compromise of the catalog domain or its CA compromises every buyer agent. Catalog-served bodies MUST be digest-pinned identically to origin fetches (the digest is on the *parent* `format_schema.uri@digest`, not on the catalog response). Future hardening (signed bodies, transparency log) is tracked separately.', - examples=[ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - title='Platform Extension Reference', - ), - ] = None - format_kind: Literal['agent_placement'] = 'agent_placement' - params: Annotated[ - Params108, - Field( - description="**3.2-track canonical.** The structural shape (algorithmic composition + brand-context input + optional offering/landing_page) is captured here so adopters can declare against it in 3.1 catalogs, but the **mention-level tracking contract is intentionally underspecified for 3.1**: no normative macro vocabulary, no postback shape, no cross-surface dedup model. Adopters claiming `agent_placement` in 3.1 ship private tracking integrations and SHOULD leave `experimental: true` on the product declaration that references this canonical; buyer agents MUST treat agent_placement attribution as adapter-defined until the 3.2 tracking-macro spec lands. The canonical promotes to a normatively-buyer-callable surface in 3.2 (or later) once the tracking contract is specified.\n\nSponsored placement integrated into an AI-surface's response to a user. Buyer supplies a `BrandRef` (resolving brand.json for context), an optional `offering_ref` to focus the mention on a specific offering, and an optional `landing_page_url` the surface MAY attach as a citation. The surface (LLM, voice assistant, sponsored-search ranker) composes a natural-language mention, sponsored card, or audio snippet within its response to a user query. **Composition is algorithmic** — the agent chooses phrasing and presentation. Output asset_type varies by surface: `text` for chat UIs and sponsored search snippets; `audio` (synthesized) for voice assistants; `card` for structured AI-surface result cards. Tracking model: mention-level impression + attribution events; per-mention id keys back to brand and offering — but see the 3.2-track note above; the wire shape of these events is not yet specified. Distinct from `si_chat` (which is the user-converses-with-brand's-agent pattern — brand owns the conversational surface) and from `sponsored_placement` (retail-media catalog-driven). Parallels `sponsored_placement` structurally: both are surface-composed placements; agent_placement is for AI/agentic surfaces, sponsored_placement is for retail media.", - title='Canonical Format: Agent Placement (AI-surface sponsored placement)', - ), - ] - - -class FormatOptions98( - RootModel[ - FormatOptions99 - | FormatOptions100 - | FormatOptions101 - | FormatOptions102 - | FormatOptions103 - | FormatOptions104 - | FormatOptions105 - | FormatOptions106 - | FormatOptions107 - | FormatOptions108 - | FormatOptions109 - | FormatOptions110 - | FormatOptions13 - ] -): - root: Annotated[ - FormatOptions99 - | FormatOptions100 - | FormatOptions101 - | FormatOptions102 - | FormatOptions103 - | FormatOptions104 - | FormatOptions105 - | FormatOptions106 - | FormatOptions107 - | FormatOptions108 - | FormatOptions109 - | FormatOptions110 - | FormatOptions13, - Field( - description='Inline format declaration on a product. The `format_kind` discriminator names which canonical format the product narrows; `params` carries the canonical\'s parameter schema (slots, dimensions, durations, codecs, character limits, platform_extensions, etc.). Optional `format_option_id` (stable identifier for routing when a product\'s `format_options` contains multiple declarations sharing the same `format_kind`), optional `publisher_domain` (namespace for the format option when it comes from a publisher adagents.json catalog), `display_name` (seller-controlled human-readable label for dashboard and catalog UIs), and `applies_to_channels` (subset of the product\'s declared channels this declaration applies to — lets a multi-channel product carry distinct format_options per channel). Discriminated-union shape generates clean tagged unions in TypeScript and Pydantic codegen. Replaces v1\'s named-format pattern (where products referenced a separately-defined format file via compound `format_id`). v1 named formats remain supported through the deprecation cycle; v2 product-bound declarations are opt-in.\n\n**Closed-set semantics (normative).** `format_options[]` is the closed set of accepted formats for this product. Sellers MUST reject `create_media_buy` requests targeting any `format_kind` (or format option reference) not present in this list — typically with `UNSUPPORTED_FEATURE` or a seller-specific code; the rejection is structural, not negotiable. `seller_preference` modulates *within* the accepted set (a soft ranking hint between equally-acceptable options), it is NOT an enforcement axis. A product wanting to say \'this format is the only one that works\' lists exactly that one entry in `format_options[]`; everything else falls outside the set and is rejected by the closed-set rule.\n\n**Format matching vs satisfaction (normative).** Legacy named formats MUST be normalized to canonical declarations before comparison; do not exact-match raw `(agent_url, id)` pairs once a `format_id` has been projected through `canonical`, `v1_format_ref`, or the canonical mapping registry. Equivalence matching can treat a legacy fixed-size display ID and `format_kind: "image"` with matching `width`/`height` as the same underlying shape. Product satisfaction is stricter and directional: when this declaration specifies fixed constraints such as `width`, `height`, `duration_ms_exact`, or `duration_ms_range`, a buyer request or creative manifest MUST declare and satisfy those constraints. A broad request with no dimensions or duration does not satisfy a fixed-size or fixed-duration product; a broad product MAY accept a more specific creative unless another product constraint excludes it. Duration precedence is `duration_ms_exact` > `duration_ms_range`. Range constraints use containment: a range-based request satisfies this declaration only when every value it permits falls within this declaration\'s accepted range; overlap alone is insufficient. An exact value satisfies a range when the exact value falls inside the accepted interval. For hosted audio/video, a null range endpoint is unbounded: [null, 60000] means up to 60s, and [15000, null] means at least 15s; [null, null] is invalid because at least one endpoint must be bounded.\n\n**Custom format_kind** (`format_kind: "custom"`): for adopter-defined shapes that don\'t fit the 12 canonicals (multi-placement takeover, roadblock, branded content, cross-screen sponsorship, sponsorship lockup, newsletter sponsorship, AR lens, playable, live event sponsorship). When `format_kind` is `custom`, the declaration MUST carry `format_shape` (recognized global pattern from the [format-shape vocabulary registry](/schemas/core/format-shape-vocabulary.json)) AND `format_schema` (URI+digest reference to a fetchable schema describing the actual `params` and `slots`). Buyer agents fetch the schema, validate manifests structurally, and reason about manifests without per-seller integration code. See [adcp#3666](https://github.com/adcontextprotocol/adcp/issues/3666) for the canonical promotion queue.', - discriminator='format_kind', - examples=[ - { - 'description': 'Meta Reels — narrows video_hosted (vertical orientation)', - 'data': { - 'format_kind': 'video_hosted', - 'params': { - 'orientation': 'vertical', - 'aspect_ratio': '9:16', - 'duration_ms_range': [3000, 90000], - 'min_width': 1080, - 'min_height': 1920, - 'max_file_size_mb': 200, - 'video_codecs': ['h264'], - 'audio_codecs': ['aac'], - 'headline_max_chars': 25, - 'primary_text_max_chars': 72, - 'captions': 'recommended', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'DOWNLOAD', 'SIGN_UP'], - 'composition_model': 'deterministic', - 'platform_extensions': [ - { - 'uri': 'https://creative.adcontextprotocol.org/translated/meta/extensions/meta_pixel', - 'digest': 'sha256:a3f5b7c9d8e2f1a4b6c8d0e2f4a6b8c0d2e4f6a8b0c2d4e6f8a0b2c4d6e8f0a2', - } - ], - }, - }, - }, - { - 'description': 'IAB Medium Rectangle (300x250) — narrows image', - 'data': { - 'format_kind': 'image', - 'params': { - 'width': 300, - 'height': 250, - 'max_file_size_kb': 200, - 'image_formats': ['jpg', 'png', 'gif'], - 'ssl_required': True, - 'composition_model': 'deterministic', - 'cta_values': ['LEARN_MORE', 'SHOP_NOW', 'GET_OFFER'], - }, - }, - }, - { - 'description': "Podcast 30s host-read — narrows audio_hosted with a `script` slot the seller's host reads verbatim. No separate `inputs` map; the script lives in the manifest's `assets` like any other text asset.", - 'data': { - 'format_kind': 'audio_hosted', - 'params': { - 'duration_ms_exact': 30000, - 'audio_codecs': ['mp3', 'aac'], - 'audio_sample_rates': [44100, 48000], - 'audio_channels': ['stereo'], - 'loudness_lufs': -16, - 'asset_source': 'publisher_host_recorded', - 'buyer_asset_acceptance': 'rejected', - 'composition_model': 'deterministic', - 'slots': [ - { - 'asset_group_id': 'script', - 'required': True, - 'asset_type': 'text', - 'max_chars': 800, - }, - { - 'asset_group_id': 'offering_ref', - 'required': False, - 'asset_type': 'text', - }, - ], - 'production_window_business_days': 7, - }, - }, - }, - { - 'description': "NYTimes Homepage Takeover — custom format_kind, classified against the multi_placement_takeover format_shape, with format_schema pointing at NYTimes's hosted schema. Buyer agents fetch the schema by uri@digest (cached, immutable) and validate the manifest structurally. `canonical_formats_only: true` is required for custom declarations — no v1 named format can express the multi-placement shape.", - 'data': { - 'format_kind': 'custom', - 'canonical_formats_only': True, - 'format_shape': 'multi_placement_takeover', - 'format_schema': { - 'uri': 'https://nytimes.example/schemas/formats/homepage_takeover_v3', - 'digest': 'sha256:e1d4f6a9c2b5e8d1f4a7c0b3e6d9f2a5c8b1e4d7f0a3c6b9e2d5f8a1c4b7e0a3', - }, - 'format_option_id': 'nytimes_homepage_takeover_premium', - 'display_name': 'Homepage Takeover — Premium Sponsorship', - 'applies_to_channels': ['display', 'olv'], - 'params': { - 'components': [ - {'placement_type': 'homepage_skin', 'required': True}, - {'placement_type': 'preroll_video', 'required': True}, - {'placement_type': 'sponsorship_lockup', 'required': True}, - ], - 'exclusivity_window_hours': 24, - 'ssl_required': True, - }, - }, - }, - ], - title='Product Format Declaration', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Placement4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[ - Kind, - Field( - description="Placement structure discriminator. `publisher_ref` identifies a placement by `{publisher_domain, placement_id}` and resolves public metadata from the named publisher's adagents.json placement declarations; `seller_inline` identifies buyer-facing placement metadata defined inline by the sales agent (still in the named publisher namespace when `publisher_domain` is present, or the seller's own namespace in legacy single-publisher contexts)." - ), - ] - placement_id: Annotated[ - str, - Field( - description="Placement identifier in the publisher namespace. When `publisher_domain` is present, this matches a placement ID in that publisher's adagents.json catalog or a seller-defined inline placement in that publisher namespace. Buyers use this with `publisher_domain` in `creative_assignments[].placement_refs`; legacy `creative_assignments[].placement_ids` strings are only unambiguous in single-publisher contexts." - ), - ] - publisher_domain: Annotated[ - str | None, - Field( - description='Publisher domain whose adagents.json placement declarations define this placement. Required for `kind: "publisher_ref"`. Omitted only for `kind: "seller_inline"` in legacy single-publisher seller contexts where the seller agent\'s own publisher domain is the namespace.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - name: Annotated[ - str | None, - Field( - description='Human-readable name for the placement (e.g., \'Homepage Banner\', \'Article Sidebar\'). Required for `kind: "seller_inline"`. May be omitted for publisher-referenced placements because buyers resolve the name from the publisher declaration identified by `{publisher_domain, placement_id}`.' - ), - ] = None - description: Annotated[ - str | None, Field(description='Detailed description of where and how the placement appears') - ] = None - mode: Annotated[ - Mode, - Field( - description="Required product-level relationship to this placement. `targetable` means the buyer may reference this placement_id when assigning creatives or otherwise selecting placements within the product. `included` means the placement is part of the product's public delivery composition but the buyer cannot cherry-pick it by placement_id. During the migration window ending 2026-11-25, buyers MAY tolerate legacy products that omit `mode` and treat them as targetable; after that date buyers SHOULD fail closed. Seller-private delivery objects MUST NOT be exposed here; keep those mappings in seller-internal systems." - ), - ] - tags: Annotated[ - list[str] | None, - Field( - description="Optional tags for grouping placements within a product (e.g., 'homepage', 'native', 'premium'). When the placement_id comes from the publisher registry, these should align with the registry tags unless the product is narrowing scope." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Format IDs supported by this specific placement. Can include: (1) concrete format_ids (fixed dimensions), (2) template format_ids without parameters (accepts any dimensions/duration), or (3) parameterized format_ids (specific dimension/duration constraints). When present on a product placement, this field narrows the product-level `format_ids` contract for this placement and MUST NOT introduce formats the product does not accept.', - min_length=1, - ), - ] = None - format_options: Annotated[ - list[FormatOptions98] | None, - Field( - description='3.1+ canonical format-option declarations supported by this specific product placement. When present, this field narrows the product-level `format_options` contract for this placement and MUST NOT introduce formats the product does not accept. Buyers compute the effective accepted formats for a placement as the intersection of product-level and placement-level declarations; placements without a format declaration inherit the product-level formats.', - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types for this product placement, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types for this product placement, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types for this product placement, distinguishing where the catalog-driven retail-media placement renders on the retailer surface. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces for this product placement, distinguishing the in-app surface where the social placement renders. Most concrete placements SHOULD declare a single value; aggregate placements MAY declare multiple values. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - - -class PriceBreakdown28(PriceBreakdown): - pass - - -class PricingOptions31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpm'], Field(description='Cost per 1,000 impressions')] = 'cpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown28 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown29(PriceBreakdown): - pass - - -class PricingOptions32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['vcpm'], Field(description='Cost per 1,000 viewable impressions (MRC standard)') - ] = 'vcpm' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown29 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown30(PriceBreakdown): - pass - - -class PricingOptions33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpc'], Field(description='Cost per click')] = 'cpc' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per click. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown30 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown31(PriceBreakdown): - pass - - -class PricingOptions34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpcv'], Field(description='Cost per completed view (100% completion)') - ] = 'cpcv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per completed view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown31 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown32(PriceBreakdown): - pass - - -class PricingOptions35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpv'], Field(description='Cost per view at threshold')] = 'cpv' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per view. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - max_bid: Annotated[ - bool | None, - Field( - description="When true, bid_price is interpreted as the buyer's maximum willingness to pay (ceiling) rather than an exact price. Sellers may optimize actual clearing prices between floor_price and bid_price based on delivery pacing. When false or absent, bid_price (if provided) is the exact bid/price to honor." - ), - ] = False - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters15, Field(description='CPV-specific parameters defining the view threshold') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown32 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown33(PriceBreakdown): - pass - - -class PricingOptions36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[Literal['cpp'], Field(description='Cost per Gross Rating Point')] = 'cpp' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Fixed price per rating point. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters4, Field(description='CPP-specific parameters for demographic targeting') - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown33 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown34(PriceBreakdown): - pass - - -class PricingOptions37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['cpa'], Field(description='Cost per acquisition (conversion event)') - ] = 'cpa' - event_type: Annotated[ - EventType, - Field( - description='The conversion event type that triggers billing (e.g., purchase, lead, app_install)' - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description="Name of the custom event when event_type is 'custom'. Required when event_type is 'custom', ignored otherwise." - ), - ] = None - event_source_id: Annotated[ - str | None, - Field( - description='When present, only events from this specific event source count toward billing. Allows different CPA rates for different sources (e.g., online vs in-store purchases). Must match an event source configured via sync_event_sources.' - ), - ] = None - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float, Field(description='Fixed price per acquisition in the specified currency', gt=0.0) - ] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown34 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown35(PriceBreakdown): - pass - - -class PricingOptions38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['flat_rate'], Field(description='Fixed cost regardless of delivery volume') - ] = 'flat_rate' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Flat rate cost. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[ - Parameters17 | None, - Field( - description='DOOH inventory allocation parameters. Sponsorship and takeover flat_rate options omit this field entirely — only include for digital out-of-home inventory.', - title='DoohParameters', - ), - ] = None - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown35 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PriceBreakdown36(PriceBreakdown): - pass - - -class PricingOptions39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pricing_option_id: Annotated[ - str, Field(description='Unique identifier for this pricing option within the product') - ] - pricing_model: Annotated[ - Literal['time'], - Field(description='Cost per time unit - rate scales with campaign duration'), - ] = 'time' - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code', - examples=['USD', 'EUR', 'GBP', 'JPY'], - pattern='^[A-Z]{3}$', - ), - ] - fixed_price: Annotated[ - float | None, - Field( - description='Cost per time unit. If present, this is fixed pricing. If absent, auction-based.', - ge=0.0, - ), - ] = None - floor_price: Annotated[ - float | None, - Field( - description='Minimum acceptable bid per time unit for auction pricing (mutually exclusive with fixed_price). Bids below this value will be rejected.', - ge=0.0, - ), - ] = None - price_guidance: Annotated[ - PriceGuidance | None, - Field( - description='Pricing guidance for auction-based bidding. Helps buyers calibrate bids with historical percentiles.', - title='Price Guidance', - ), - ] = None - parameters: Annotated[Parameters18, Field(description='Time-based pricing parameters')] - min_spend_per_package: Annotated[ - float | None, - Field( - description='Minimum spend requirement per package using this pricing option, in the specified currency', - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown36 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - eligible_adjustments: Annotated[ - list[PriceAdjustmentKind] | None, - Field( - description='Adjustment kinds applicable to this pricing option. Tells buyer agents which adjustments are available before negotiation. When absent, no adjustments are pre-declared — the buyer should check price_breakdown if present.' - ), - ] = None - - -class PricingOptions30( - RootModel[ - PricingOptions31 - | PricingOptions32 - | PricingOptions33 - | PricingOptions34 - | PricingOptions35 - | PricingOptions36 - | PricingOptions37 - | PricingOptions38 - | PricingOptions39 - ] -): - root: Annotated[ - PricingOptions31 - | PricingOptions32 - | PricingOptions33 - | PricingOptions34 - | PricingOptions35 - | PricingOptions36 - | PricingOptions37 - | PricingOptions38 - | PricingOptions39, - Field( - description="A pricing model option offered by a publisher for a product. Discriminated by pricing_model field. If fixed_price is present, it's fixed pricing. If absent, it's auction-based (floor_price and price_guidance optional). Bid-based auction models may also include max_bid as a boolean signal to interpret bid_price as a buyer ceiling instead of an exact honored price.", - discriminator='pricing_model', - title='Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Dimensions38(Dimensions8): - pass - - -class Dimensions40(Dimensions10): - pass - - -class Dimensions41(Dimensions11): - pass - - -class Dimensions42(Dimensions12): - pass - - -class EmbeddedProvenanceItem253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent506 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent507 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction263(Jurisdiction229): - pass - - -class Disclosure254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction263] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance253(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy253 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem253] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark253] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure254 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem253] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance253 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride50(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo50 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor31(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride50 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor31 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent508 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent509 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction264(Jurisdiction229): - pass - - -class Disclosure255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction264] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance254(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy254 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem254] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark254] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure255 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem254] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance254 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride51(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo51 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor32(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride51 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue9(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor32, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions38 | Dimensions39 | Dimensions40 | Dimensions41 | Dimensions42 | Dimensions43 - ] - | None, - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] = None - metrics: Annotated[ - Metrics7, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability11 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue9] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class Forecast5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - points: Annotated[ - list[Point6], - Field( - description='Forecasted delivery data points. For spend curves (default), points at ascending budget levels show how metrics scale with spend. For availability forecasts, points represent total available inventory independent of budget. See forecast_range_unit for interpretation.', - min_length=1, - ), - ] - forecast_range_unit: ForecastRangeUnit | None = None - method: ForecastMethod - currency: Annotated[ - str, - Field( - description='ISO 4217 currency code for monetary values in this forecast (spend, budget)' - ), - ] - demographic_system: DemographicSystem | None = None - demographic: Annotated[ - str | None, - Field( - description='Target demographic code within the specified demographic_system. For Nielsen: P18-49, M25-54, W35+. For BARB: ABC1 Adults, 16-34. For AGF: E 14-49.', - examples=['P18-49', 'A25-54', 'W35+', 'M18-34'], - ), - ] = None - measurement_source: Annotated[ - str | None, - Field( - description='Third-party measurement provider whose data was used to produce this forecast. Distinct from demographic_system, which specifies demographic notation — measurement_source identifies whose data produced the forecast numbers. Should be present when measured_impressions is used. Lowercase slug format.', - examples=[ - 'nielsen', - 'videoamp', - 'comscore', - 'geopath', - 'barb', - 'agf', - 'oztam', - 'kantar', - 'barc', - 'route', - 'rajar', - 'triton', - ], - max_length=64, - pattern='^[a-z0-9_]+$', - ), - ] = None - reach_unit: ReachUnit | None = None - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this forecast was computed') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this forecast expires. After this time, the forecast should be refreshed. Forecast expiry does not affect proposal executability.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent510 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent511 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction265(Jurisdiction229): - pass - - -class Disclosure256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction265] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy255 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem255] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark255] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure256 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem255] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance255 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride52(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo52 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor33(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride52 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class DeliveryMeasurement3(AdCPBaseModel): - vendors: Annotated[ - list[Vendor33] | None, - Field( - description="Measurement vendors used for this product, as structured `BrandRef` identities. Multiple entries when multiple vendors play different roles (e.g., the ad server plus a separate viewability vendor like IAS or DV; or a retail-media seller plus a third-party retail measurement vendor like Circana or NielsenIQ). Each vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Distinct from `performance_standards[].vendor` which carries vendor identity for *committed* metrics with thresholds — this field carries vendor identity for the overall measurement story, including non-committed-but-reported metrics.", - min_length=1, - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="**Deprecated as of this minor.** Free-form measurement provider description (e.g., 'Google Ad Manager with IAS viewability', 'Nielsen DAR', 'Geopath for DOOH impressions'). New implementations SHOULD use the structured `vendors` field instead. Retained for one-minor backwards compatibility; removed at the next major. When both `vendors` and `provider` are present, consumers MUST use `vendors` for vendor identity and treat `provider` as informational text." - ), - ] = None - notes: Annotated[ - str | None, - Field( - description="Additional details about measurement methodology in plain language (e.g., 'MRC-accredited viewability. 50% in-view for 1s display / 2s video', 'Panel-based demographic measurement updated monthly'). Free-form prose for context that doesn't fit the structured `vendors` field." - ), - ] = None - - -class EmbeddedProvenanceItem256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent512 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent513 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction266(Jurisdiction229): - pass - - -class Disclosure257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction266] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy256 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem256] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark256] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure257 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem256] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance256 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride53(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo53 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor34(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride53 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor34, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement3 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent514 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent515 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction267(Jurisdiction229): - pass - - -class Disclosure258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction267] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy257 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem257] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark257] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure258 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem257] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance257 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride54(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo54 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor35(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride54 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor35, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class AllowedAction3(AllowedAction): - pass - - -class EmbeddedProvenanceItem258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent516 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent517 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction268(Jurisdiction229): - pass - - -class Disclosure259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction268] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy258 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem258] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark258] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure259 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem258] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance258 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride55(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo55 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor36(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride55 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetric3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor36, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's standard alignment, accreditations, and methodology live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_units`, `gco2e_per_impression`, `demographic_reach`).", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - - -class ReportingCapabilities3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - available_reporting_frequencies: Annotated[ - list[ReportingFrequency], - Field(description='Supported reporting frequency options', min_length=1), - ] - expected_delay_minutes: Annotated[ - int, - Field( - description='Expected delay in minutes before reporting data becomes available (e.g., 240 for 4-hour delay)', - examples=[240, 300, 1440], - ge=0, - ), - ] - timezone: Annotated[ - str, - Field( - description="Timezone for reporting periods. Use 'UTC' or IANA timezone (e.g., 'America/New_York'). Critical for daily/monthly frequency alignment.", - examples=['UTC', 'America/New_York', 'Europe/London', 'America/Los_Angeles'], - ), - ] - supports_webhooks: Annotated[ - bool, - Field(description='Whether this product supports webhook-based reporting notifications'), - ] - available_metrics: Annotated[ - list[AvailableMetric], - Field( - description="Metrics available in reporting. Impressions and spend are always implicitly included. When a creative format declares reported_metrics, buyers receive the intersection of these product-level metrics and the format's reported_metrics.", - examples=[ - ['impressions', 'spend', 'clicks', 'completed_views'], - ['impressions', 'spend', 'conversions'], - ], - ), - ] - vendor_metrics: Annotated[ - list[VendorMetric3] | None, - Field( - description="Vendor-defined metrics this product can report, beyond the closed `available_metrics` enum. Each entry is a pointer (`{ vendor, metric_id }`) into the vendor's metric catalog — the canonical definition (standard alignment, accreditations, methodology, unit, human-readable description) lives at the vendor's `get_adcp_capabilities.measurement.metrics[]`, queried once per vendor when needed. Use this for proprietary metrics like attention scores, emissions, panel-based demographics, or platform-native social metrics not yet in the standard enum. Sellers populate values in delivery via `delivery-metrics.json#/properties/vendor_metric_values`. The metric is identified by the tuple `(vendor, metric_id)`; identifiers are namespaced by the vendor, so the same `metric_id` may mean different things in different vendors' vocabularies. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before emission and MUST NOT declare the same vendor metric twice. Buyers MAY treat duplicate `(vendor, metric_id)` rows as a seller-side conformance bug. (JSON Schema `uniqueItems` is not used here because BrandRef carries optional fields whose absence/presence would defeat deep-equal — uniqueness is on the semantic key, enforced at build/validation time on the seller side.) Promotion path: when the industry converges on a metric via a published standard, the spec adds it to the closed `available_metrics` enum and the vendor extensions become historical aliases." - ), - ] = None - supports_creative_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports creative-level metric breakdowns in delivery reporting (by_creative within by_package)' - ), - ] = None - supports_keyword_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports keyword-level metric breakdowns in delivery reporting (by_keyword within by_package)' - ), - ] = None - supports_geo_breakdown: Annotated[ - SupportsGeoBreakdown3 | None, - Field( - description='Geographic breakdown support for this product. Declares which geo levels and systems are available for by_geo reporting within by_package.', - title='Geographic Breakdown Support', - ), - ] = None - supports_device_type_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device type breakdowns in delivery reporting (by_device_type within by_package)' - ), - ] = None - supports_device_platform_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports device platform breakdowns in delivery reporting (by_device_platform within by_package)' - ), - ] = None - supports_audience_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports audience segment breakdowns in delivery reporting (by_audience within by_package)' - ), - ] = None - supports_placement_breakdown: Annotated[ - bool | None, - Field( - description='Whether this product supports placement breakdowns in delivery reporting (by_placement within by_package)' - ), - ] = None - date_range_support: Annotated[ - DateRangeSupport, - Field( - description="Whether delivery data can be filtered to arbitrary date ranges. 'date_range' means the platform supports start_date/end_date parameters. 'lifetime_only' means the platform returns campaign lifetime totals and date range parameters are not accepted." - ), - ] - windowed_pull_granularities: Annotated[ - list[ReportingFrequency] | None, - Field( - description='Granularities at which this product honors per-window pulls on get_media_buy_delivery (via request `time_granularity` + `include_window_breakdown: true`). Closes the GET-side half of the snapshot/log two-paths-parity contract for data-bearing events: a buyer who missed a webhook fire at any granularity listed here can reconstruct an identical payload by polling. Capability-scoped MUST — sellers MUST honor pulls at any granularity declared here, and MUST return UNSUPPORTED_GRANULARITY for pulls outside the set. Sellers MAY emit higher-frequency webhooks than they expose for pull (common where the webhook is a Kafka tap and historical reads go through a warehouse with coarser granularity); buyers see the gap up front via this capability and treat the webhook as primary for those frequencies. Absent or empty means the product only supports cumulative date-range pulls and full per-window recovery via GET is unavailable — see snapshot-and-log Rule 4.', - examples=[['daily'], ['hourly', 'daily'], ['hourly', 'daily', 'monthly']], - ), - ] = None - measurement_windows: Annotated[ - list[MeasurementWindow] | None, - Field( - description='Measurement maturation stages available for this product. Used by any channel where billing-grade data is produced in phases rather than arriving final on day one. Examples: broadcast/linear TV (Live → C3 → C7 DVR accumulation), DOOH (tentative plays → post-IVT/fraud-check final), digital with IVT filtering (raw → GIVT filtered → SIVT filtered), podcast (7-day downloads → 30-day downloads). Each window defines an accumulation period and expected data availability. When present, delivery reports reference a specific window_id. Sellers whose data is final on first delivery typically omit this.', - min_length=1, - ), - ] = None - - -class CreativePolicy4(CreativePolicy): - pass - - -class IncludedSignal3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - - -class SignalTargetingOption3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] - signal_id: Annotated[ - SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this listing record was last updated. This indicates freshness of the listing record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_agent_segment_id: Annotated[ - str | None, - Field( - description='Optional opaque resolved-segment or seller execution handle for this signal. Omit when signal_ref plus the value expression is sufficient for the seller to resolve the signal. Include when the seller exposes a distinct runtime or activation handle that buyers must echo in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].signal_agent_segment_id. Buyers SHOULD echo this handle verbatim rather than reconstructing identity from categorical values; providers MAY namespace handles so cross-provider identity stays legible without a shared taxonomy registry.' - ), - ] = None - activation_status: Annotated[ - ActivationStatus | None, - Field( - description="Whether this signal option is ready to select on create_media_buy for the requesting account. 'ready' means the buyer can select it directly. 'requires_activation' means the buyer must activate the signal first or include an activation_key the seller accepts." - ), - ] = ActivationStatus.ready - allowed_targeting_modes: Annotated[ - list[AllowedTargetingMode] | None, - Field( - description="How this signal may be used when composing package-level signal targeting groups. 'include' means the signal may appear in an 'any' child group. 'exclude' means the signal may appear in a 'none' child group. Omit when the signal is include-only. This field declares the allowed buy-time group operator; binary package signal entries still use value=true in both include and exclude groups.", - min_length=1, - ), - ] = [AllowedTargetingMode.include] - default_selected: Annotated[ - bool | None, - Field( - description="Whether the seller recommends or preselects this signal when composing this product. Buyers may remove it unless signal_targeting_rules.selection_mode is 'fixed'. When selection_mode is 'fixed', sellers apply default_selected signals even if the buyer omits signal_targeting_groups and MUST echo the applied entries on the resulting package state." - ), - ] = False - selection_group: Annotated[ - str | None, - Field( - description='Optional product-defined composability bucket for signal options, such as alternative audience tiers, a key-value targeting plane, or an audience-segment targeting plane. Signals in the same selection_group are expected to be OR-combinable inside one child group for a given targeting mode, subject to signal_targeting_rules. Use different selection_group values when the product requires separate ANDed clauses, such as signal sets backed by different platform targeting primitives that cannot be collapsed into one child group. selection_group is a product-option grouping key, not a reference to one child object in packages[].targeting_overlay.signal_targeting_groups.groups[]. Sellers can use signal_targeting_rules.max_selected_per_group and signal_targeting_rules.selection_group_rules with selection_group to guide and validate storefront composition.' - ), - ] = None - pricing_options: Annotated[ - list[PricingOption17] | None, - Field( - description='Signal pricing options available when this signal is selected on this product. Product-scoped pricing is authoritative for this product; if get_signals exposes a different default rate card, use this product-scoped price when composing the buy. Buyers pass the selected pricing_option_id in packages[].targeting_overlay.signal_targeting_groups.groups[].signals[].pricing_option_id. Omit when the signal is bundled into the product price or has no incremental cost.', - min_length=1, - ), - ] = None - - -class MetricOptimization3(MetricOptimization): - pass - - -class EmbeddedProvenanceItem259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent518 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent519 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction269(Jurisdiction229): - pass - - -class Disclosure260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction269] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy259 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem259] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark259] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure260 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem259] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance259 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride56(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo56 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor37(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride56 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class SupportedMetric7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor37, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` is the discovery anchor for the measurement agent (entry with `type: 'measurement'` in the `agents[]` array); the metric's definition, methodology, and unit live at that agent's `get_adcp_capabilities.measurement.metrics[]` and are not duplicated inline here. Same shape as the `vendor` field on `reporting_capabilities.vendor_metrics` for symmetry across optimization and reporting capability declarations.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - supported_targets: Annotated[ - list[SupportedTarget] | None, - Field( - description='Target kinds available for `vendor_metric` goals against this `(vendor, metric_id)` pair. Values match `target.kind` on the optimization goal. `cost_per` — target cost per metric unit (e.g., $0.05 per attention-second). `threshold_rate` — minimum per-impression value (e.g., attention_score ≥ 70). Only these target kinds are accepted — goals with unlisted target kinds will be rejected. A goal without a target implicitly maximizes the metric within budget — no declaration needed for that mode. When omitted, buyers can still set target-less vendor_metric goals.' - ), - ] = None - - -class VendorMetricOptimization5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported_metrics: Annotated[ - list[SupportedMetric7], - Field( - description="Vendor-defined metrics this product can steer delivery toward. Each entry pairs a vendor identity (BrandRef anchored on the vendor's `brand.json` `agents[type='measurement']`) with a `metric_id` from that vendor's published `measurement.metrics[]` catalog, plus the target kinds the seller supports for the pair. Semantic uniqueness key is `(vendor.domain, vendor.brand_id, metric_id)`; sellers MUST de-duplicate before publication. JSON Schema `uniqueItems` blocks exact-object duplicates; semantic deduplication on the BrandRef-domain key is a seller obligation." - ), - ] - - -class MeasurementReadiness3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: AssessmentStatus - required_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product needs for effective optimization. Buyers should ensure their event sources cover these types.', - min_length=1, - ), - ] = None - missing_event_types: Annotated[ - list[EventType] | None, - Field( - description='Event types this product requires that the buyer has not configured. Empty or absent when all required types are covered.' - ), - ] = None - issues: Annotated[ - list[Issue22] | None, - Field( - description='Actionable issues preventing full measurement readiness. Sellers should limit to the top 3-5 most actionable items. Buyer agents should sort by severity rather than relying on array position.' - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Seller explanation of the readiness assessment, recommendations for improvement, or context about what the buyer needs to change.' - ), - ] = None - - -class ConversionTracking5(ConversionTracking): - pass - - -class EmbeddedProvenanceItem260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent520 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent521 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction270(Jurisdiction229): - pass - - -class Disclosure261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction270] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance260(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy260 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem260] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark260] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure261 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem260] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Image3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance260 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCard3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - image: Annotated[ - Image3 | None, - Field( - description='Hero image for the card. Recommended ~300x400 (4:3 portrait) for the standard card layout; receivers may scale.', - title='Image Asset', - ), - ] = None - title: Annotated[ - str | None, Field(description='Card title (typically the product name).', max_length=60) - ] = None - description: Annotated[ - str | None, - Field(description='Short descriptive blurb shown below the title.', max_length=200), - ] = None - price_label: Annotated[ - str | None, - Field( - description="Formatted price or pricing summary (e.g., 'From $5 CPM', 'Auction floor $0.50 CPC'). Free-text — receivers render verbatim.", - max_length=30, - ), - ] = None - cta_label: Annotated[ - str | None, - Field( - description="Call-to-action button label (e.g., 'View details', 'Get proposal').", - max_length=25, - ), - ] = None - - -class EmbeddedProvenanceItem261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent522 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent523 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction271(Jurisdiction229): - pass - - -class Disclosure262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction271] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy261 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem261] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark261] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure262 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem261] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HeroImage3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance261 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent524 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent525 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction272(Jurisdiction229): - pass - - -class Disclosure263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction272] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy262 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem262] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark262] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure263 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem262] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CarouselImage3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance262 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class ProductCardDetailed3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - hero_image: Annotated[ - HeroImage3 | None, - Field( - description='Primary hero image at the top of the detailed view.', title='Image Asset' - ), - ] = None - carousel_images: Annotated[ - list[CarouselImage3] | None, - Field(description='Additional images for a swipeable carousel below the hero.'), - ] = None - title: Annotated[str | None, Field(description='Page title (typically the product name).')] = ( - None - ) - description: Annotated[ - str | None, - Field( - description='Full descriptive copy. Markdown allowed in client renderers that support it; otherwise treat as plain text.' - ), - ] = None - specifications: Annotated[ - list[Specification] | None, - Field( - description="Structured key/value specifications (e.g., 'Aspect ratio: 9:16', 'Duration: 30s'). Each item is a labeled fact about the product." - ), - ] = None - price_label: Annotated[str | None, Field(description='Formatted price or pricing summary.')] = ( - None - ) - cta_label: Annotated[str | None, Field(description='Call-to-action button label.')] = None - - -class Installment3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - installment_id: Annotated[ - str, Field(description='Unique identifier for this installment within the collection') - ] - collection_id: Annotated[ - str | None, - Field( - description="Parent collection reference. Required when the product spans multiple collections. Maps to a collection_id declared in one of the publishers' adagents.json files referenced by the product's collection selectors." - ), - ] = None - name: Annotated[str | None, Field(description='Installment title')] = None - season: Annotated[ - str | None, Field(description="Season identifier (e.g., '1', '2024', 'spring_2026')") - ] = None - installment_number: Annotated[ - str | None, Field(description="Installment number within the season (e.g., '3', '47')") - ] = None - scheduled_at: Annotated[ - AwareDatetime | None, Field(description='When the installment airs or publishes (ISO 8601)') - ] = None - status: InstallmentStatus | None = None - duration_seconds: Annotated[ - int | None, Field(description='Expected duration of the installment in seconds', ge=0) - ] = None - flexible_end: Annotated[ - bool | None, Field(description='Whether the end time is approximate (live events, sports)') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='When this installment data expires and should be re-queried. Agents should re-query before committing budget to products with tentative installments.' - ), - ] = None - content_rating: Annotated[ - ContentRating | None, - Field( - description="Installment-specific content rating. Overrides the collection's baseline content_rating when present.", - title='Content Rating', - ), - ] = None - topics: Annotated[ - list[str] | None, - Field( - description="Content topics for this installment. Uses the same taxonomy as the collection's genre_taxonomy when present. Enables installment-level brand safety evaluation beyond content_rating." - ), - ] = None - special: Annotated[ - Special | None, - Field( - description='Installment-specific event context. When present, this installment is anchored to a real-world event. Overrides the collection-level special when present.', - title='Special', - ), - ] = None - guest_talent: Annotated[ - list[GuestTalentItem] | None, - Field( - description="Installment-specific guests and talent. Additive to the collection's recurring talent." - ), - ] = None - ad_inventory: Annotated[ - AdInventory | None, - Field( - description='Break-based ad inventory for this installment. For non-break formats (host reads, integrations), use product placements.', - title='Ad Inventory Configuration', - ), - ] = None - deadlines: Annotated[ - Deadlines3 | None, - Field( - description='Booking, cancellation, and material submission deadlines for this installment. Present when the installment has time-sensitive inventory that requires advance commitment or material delivery.', - title='Installment Deadlines', - ), - ] = None - derivative_of: Annotated[ - DerivativeOf | None, - Field( - description='When this installment is a clip, highlight, or recap derived from a full installment. The source installment_id must reference an installment within the same response.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Provider8(Provider): - pass - - -class TrustedMatch5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - context_match: Annotated[ - bool, - Field( - description="Whether this product supports Context Match requests. When true, the publisher's TMP router will send context match requests to registered providers for this product's inventory." - ), - ] - identity_match: Annotated[ - bool | None, - Field( - description="Whether this product supports Identity Match requests. When true, the publisher's TMP router will send identity match requests to evaluate user eligibility." - ), - ] = False - response_types: Annotated[ - list[TMPResponseType] | None, - Field(description='What the publisher can accept back from context match.', min_length=1), - ] = [TMPResponseType.activation] - dynamic_brands: Annotated[ - bool | None, - Field( - description="Whether the buyer can select a brand at match time. When false (default), the brand must be specified on the media buy/package. When true, the buyer's offer can include any brand — the publisher applies approval rules at match time. Enables multi-brand agreements where the holding company or buyer agent selects brand based on context." - ), - ] = False - providers: Annotated[ - list[Provider8] | None, - Field( - description="TMP providers integrated with this product's inventory. Each entry identifies a provider by agent_url (from the registry) and declares what match types it supports for this product. The product-level context_match and identity_match booleans declare what the product supports overall; the per-provider booleans declare which provider handles each match type. Enables buyer discovery: 'find products where a specific provider does context matching.'", - min_length=1, - ), - ] = None - - -class PartialResults1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Unique identifier for the product')] - name: Annotated[str, Field(description='Human-readable product name')] - description: Annotated[ - str, Field(description='Detailed description of the product and its inventory') - ] - publisher_properties: Annotated[ - list[PublisherProperty10], - Field( - description="SDK implementers MUST enforce singular-only at runtime: each entry uses the singular `publisher_domain` form; the compact `publisher_domains[]` form is rejected on products. Codegen toolchains (json-schema-to-typescript, quicktype, datamodel-code-generator, openapi-typescript-codegen) often flatten the `allOf + $ref + not.required` restriction below poorly and may drop the rejection constraint silently, emitting an unrestricted type — runtime enforcement is the safety net. Publisher properties covered by this product. Buyers fetch actual property definitions from each publisher's adagents.json and validate agent authorization. Selection patterns mirror the authorization patterns in adagents.json for consistency. The compact `publisher_domains[]` form is reserved for adagents.json `authorized_agents[].publisher_properties[]` so that buy-side traffic-and-pricing flatteners can always treat each entry as exactly one publisher.", - min_length=1, - ), - ] - channels: Annotated[ - list[MediaChannel] | None, - Field( - description="Advertising channels this product is sold as. Products inherit from their properties' supported_channels but may narrow the scope. For example, a product covering YouTube properties might be sold as ['ctv'] even though those properties support ['olv', 'social', 'ctv']." - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description="Legacy named-format path: array of supported creative format IDs (structured format_id objects with agent_url and id). Products MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. Named formats predate 3.1 and remain supported through the deprecation calendar (2027-Q4 floor / 2029-Q1 ceiling).\n\n**Dual emission**: A product MAY carry both `format_ids` and `format_options` simultaneously during the migration window. This is the recommended seller pattern — author once, SDK projects to both wire shapes via the [canonical mapping registry](/schemas/registries/v1-canonical-mapping.json), every buyer reads what it knows. When both are present, the two MUST refer to the SAME underlying format declaration (the `format_options[i]` narrows the canonical that the named format in `format_ids[i]` resolves to via the registry / explicit `canonical` field). SDKs that derive both shapes from one source guarantee this invariant; SDKs that don't MUST treat divergence as a build error and refuse to emit. **Buyer rule**: when both are present, prefer `format_options`; treat `format_ids` as fallback for legacy-format buyers. **Non-projectable formats**: when a named format has no clean 3.1+ format-option projection (no registry entry, no explicit `canonical` declaration on the named format, no structural match), SDKs MUST NOT emit `format_options` for that product — only `format_ids` ships, and the product remains legacy-format-only until the seller adds an explicit `canonical` field or files a registry entry." - ), - ] = None - format_options: Annotated[ - list[FormatOptions84], - Field( - description="3.1+ format-option path: one or more inline format declarations the product accepts. Each element narrows a canonical format with parameters, slots, and platform_extensions. The 90% case is a single-element array (one canonical narrowed for the product). Multi-element use cases: a product that accepts EITHER a third-party-hosted creative (for example, externally served `html5`) OR an internal `display_tag`; a video product that accepts a hosted `video_hosted` upload OR a `video_vast` tag. Buyers pick which option they're shipping at `sync_creatives` time by aligning their manifest to the matching declaration's `format_kind` and slots.\n\nProducts MUST carry `format_ids`, `format_options`, or BOTH; at least one is required. See `format_ids` description for the dual-emission contract (same underlying declaration when both are present; SDK derives one from the other; buyers prefer `format_options` when both are present).\n\nWhen `placements[]` also declare `format_ids` or `format_options`, product-level formats are the upper bound for the sellable product. Placement-level formats narrow the product-wide accepted set for that placement; they MUST NOT introduce a format the product does not accept. Buyers compute the effective accepted set for a placement as the intersection of product-level and placement-level declarations. For format options, match publisher-declared options by `{ publisher_domain, format_option_id }`, match product-local options by `format_option_id` when `publisher_domain` is omitted, and otherwise match declarations with the same `format_kind` whose placement parameters narrow the product declaration. If a placement has no format declaration, it inherits the product-level formats.", - min_length=1, - ), - ] - placements: Annotated[ - list[Placement4] | None, - Field( - description="Optional array of specific public placements within this product. Placement IDs are scoped by publisher domain. Product placements declare `kind` to distinguish publisher-referenced placements (`publisher_ref`) from seller-defined inline placements (`seller_inline`). Publisher-referenced placements carry `publisher_domain` plus `placement_id` and may omit `name` because buyers resolve the name from the publisher's adagents.json placement declarations. Seller-inline placements carry buyer-facing `name` directly; when `publisher_domain` is omitted, buyers MAY interpret the placement ID relative to the seller agent's own publisher domain only during the legacy single-publisher transition. Community-maintained fallback files are resolver/source metadata, not a distinct placement kind. Each placement MUST declare `mode: 'targetable'` (buyer may select the placement by PlacementRef, for example in creative assignments) or `mode: 'included'` (part of the public product composition but not buyer-selectable). Placement-level format declarations narrow the product-level creative contract and MUST NOT broaden it. Seller-private delivery objects, source/origin details, and ad-server mappings MUST NOT be exposed here.", - min_length=1, - ), - ] = None - video_placement_types: Annotated[ - list[VideoPlacementType] | None, - Field( - description='Declared video placement types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 video.plcmt definitions with AdCP-native names. Use on OLV, CTV, and other video products when buyers need to distinguish instream, accompanying-content, interstitial, and standalone/no-content inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `video_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - audio_distribution_types: Annotated[ - list[AudioDistributionType] | None, - Field( - description='Declared audio distribution types that may be included in this product, using IAB Tech Lab/OpenRTB 2.6 audio.feed definitions with AdCP-native names. Use on radio, streaming-audio, podcast, gaming, and other audio products when buyers need to distinguish music streaming services, FM/AM broadcast, podcasts, catch-up radio, web radio, video-game audio, and text-to-speech inventory without changing the buyer-facing channel or adagents.json property type. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `audio_distribution_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - sponsored_placement_types: Annotated[ - list[SponsoredPlacementType] | None, - Field( - description='Declared sponsored-placement types that may be included in this product, distinguishing where catalog-driven retail-media placements render on the retailer surface (sponsored search, sponsored display, or sponsored native). Use on retail-media products when buyers need to distinguish search-keyed, display, and native in-grid sponsored inventory. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `sponsored_placement_types`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - social_placement_surfaces: Annotated[ - list[SocialPlacementSurface] | None, - Field( - description='Declared social-placement surfaces that may be included in this product, distinguishing the in-app surface where social placements render (feed, stories, short_video, explore, or search). Use on social products when buyers need to distinguish feed, story, short-video, and discovery surfaces. Aggregate products and ad-network products MAY declare multiple values. When `placements[]` also carry `social_placement_surfaces`, this product-level array SHOULD be the union of the placement-level declarations the seller may deliver under the product. This is seller-declared discovery metadata, not independent verification of inventory quality or delivery context.', - min_length=1, - ), - ] = None - delivery_type: DeliveryType - exclusivity: Exclusivity | None = None - pricing_options: Annotated[ - list[PricingOptions30], - Field(description='Available pricing models for this product', min_length=1), - ] - forecast: Annotated[ - Forecast5 | None, - Field( - description='Forecasted delivery metrics for this product. Gives buyers an estimate of expected performance before requesting a proposal.', - title='Delivery Forecast', - ), - ] = None - outcome_measurement: Annotated[ - OutcomeMeasurement3 | None, - Field( - description='**Deprecated as of this minor.** Outcome capabilities (incremental sales lift, brand lift, foot traffic, etc.) are now declared via `reporting_capabilities.available_metrics` (the same path used for impressions, conversions, ROAS) with `qualifier.attribution_methodology` and `qualifier.attribution_window` carrying the methodology and window on commit. New implementations SHOULD use the unified pattern; this field is retained for one-minor backwards compatibility and removed at the next major. See `outcome-measurement.json` description for migration guidance.', - title='Outcome Measurement (Deprecated)', - ), - ] = None - delivery_measurement: Annotated[ - DeliveryMeasurement3 | None, - Field( - description='Measurement vendors and methodology for delivery metrics. The buyer accepts the declared vendors as the source of truth for the buy. When absent, buyers should apply their own measurement defaults. Senders SHOULD populate `vendors` (structured BrandRef array) for new implementations; the legacy `provider` string field is deprecated and retained for one-minor backwards compatibility.' - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms3 | None, - Field( - description="Seller's default billing measurement and makegood terms. Declares who counts the billing metric and what remedies apply when thresholds are breached. Buyers may propose different terms at media buy creation — sellers accept, reject (TERMS_REJECTED), or adjust per their policy.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard3] | None, - Field( - description="Seller's default performance standards for this product: viewability, IVT, completion rate, brand safety, attention score. Buyers may propose different standards at media buy creation. When absent, no structured performance standards apply.", - min_length=1, - ), - ] = None - cancellation_policy: Annotated[ - CancellationPolicy4 | None, - Field( - description='Cancellation terms for this product. Declares the minimum notice period required before cancellation takes effect and any penalties for insufficient notice. Relevant for guaranteed delivery products. Buyers accept these terms by creating a media buy against the product.', - title='Cancellation Policy', - ), - ] = None - allowed_actions: Annotated[ - list[AllowedAction3] | None, - Field( - description='Actions buyers may perform on buys created against this product, scoped to statuses and modes. Advisory template — the authoritative per-buy capability is `available_actions[]` on the buy response, which resolves modes against current buy state, account tier, and negotiated terms. Buyers SHOULD use this for pre-flight product selection ("which products let me self-serve cancel within 72hr?") and read `available_actions[]` for runtime decisions. The array is uniquely keyed by `action` — sellers MUST NOT emit two entries with the same `action` value. Absence means the seller has not declared a structured action surface for this product — buyers fall back to `valid_actions[]` on buy responses for the flat string vocabulary.', - min_length=1, - ), - ] = None - reporting_capabilities: Annotated[ - ReportingCapabilities3, - Field( - description='Reporting capabilities available for a product', - title='Reporting Capabilities', - ), - ] - creative_policy: Annotated[ - CreativePolicy4 | None, - Field( - description='Creative requirements and restrictions for a product', - title='Creative Policy', - ), - ] = None - is_custom: Annotated[bool | None, Field(description='Whether this is a custom product')] = None - property_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can filter this product to a subset of its publisher_properties. When false (default), the product is 'all or nothing' - buyers must accept all properties or the product is excluded from property_list filtering results." - ), - ] = False - data_provider_signals: Annotated[ - list[DataProviderSignalSelector] | None, - Field( - deprecated=True, - description='Deprecated. Legacy/non-selectable metadata for data-provider signals already bundled into or associated with this product. This field does not provide buyer-selectable options, prices, or seller activation handles. Use included_signals for non-selectable product signal metadata, or signal_targeting_options for selectable package-level signal groups.', - ), - ] = None - included_signals: Annotated[ - list[IncludedSignal3] | None, - Field( - description="Non-selectable signal metadata for signals already included in, bundled with, or planned into this product. These signals describe what the product is; buyers do not select them in packages[].targeting_overlay.signal_targeting_groups and this field does not imply package-level signal targeting. Use signal_ref scope 'data_provider' or 'signal_source' to reference externally defined signals without redefining their name or value_type. Use signal_ref scope 'product' with name and value_type when the included signal is defined only by this product.", - min_length=1, - ), - ] = None - signal_targeting_options: Annotated[ - list[SignalTargetingOption3] | None, - Field( - description="Inline seller-offered signals that may be applied to packages for this product at create_media_buy time. Each entry references a named signal definition with signal_ref scope 'product' for a product-local signal option, scope 'data_provider' for an external signal definition published in adagents.json signals[] that the seller is authorized to apply, or scope 'signal_source' for a source-native signal. Product-local options define name and value_type inline; data-provider and signal-source options may omit those fields when the referenced definition or source is authoritative. Use this field when the selectable menu is product-specific, has product-specific pricing or activation handles, is the relevant subset for a brief/refine result, or should be rendered without an additional get_signals call. Wholesale products may omit this field and rely on get_signals for the selectable signal feed. Buyers select eligible signals through packages[].targeting_overlay.signal_targeting_groups when signal_targeting_rules allow; fixed/default entries are applied by the seller and echoed on the package state. Sellers MUST set signal_targeting_allowed to true whenever this field is present. Bundled, non-selectable signal metadata belongs in included_signals; legacy data_provider_signals may appear only for backwards compatibility.", - min_length=1, - ), - ] = None - signal_targeting_rules: Annotated[ - SignalTargetingRules3 | None, - Field( - description='Composition rules for selecting signals on this product. The selectable signal menu may come from inline signal_targeting_options or from get_signals when a wholesale product omits inline options. This is product-scoped because products may be backed by different ad servers with different Boolean targeting support and group limits.', - title='Signal Targeting Rules', - ), - ] = None - signal_targeting_allowed: Annotated[ - bool | None, - Field( - description='Whether this product has a package-level signal_targeting_groups surface. When false (default), signals are bundled into the product terms and cannot be selected or explicitly echoed as package signal groups. When true, eligible signals from inline signal_targeting_options or from get_signals may be buyer-selected or seller-applied according to signal_targeting_rules and are represented through packages[].targeting_overlay.signal_targeting_groups. Editability is controlled by signal_targeting_rules; fixed/default-only products still set this to true when applied signal groups are echoed.' - ), - ] = False - catalog_types: Annotated[ - list[CatalogType] | None, - Field( - description='Catalog types this product supports for catalog-driven campaigns. A sponsored product listing declares ["product"], a job board declares ["job", "offering"]. Buyers match synced catalogs to products via this field.', - min_length=1, - ), - ] = None - metric_optimization: Annotated[ - MetricOptimization3 | None, - Field( - description="Metric optimization capabilities for this product. Presence indicates the product supports optimization_goals with kind: 'metric'. No event source or conversion tracking setup required — the seller tracks these metrics natively." - ), - ] = None - vendor_metric_optimization: Annotated[ - VendorMetricOptimization5 | None, - Field( - description="Vendor-attested metric optimization capabilities for this product. Presence indicates the product supports `optimization_goals` with `kind: 'vendor_metric'` — the seller's bidding stack can steer delivery toward a specific vendor's measurement (e.g., DV/IAS/Adelaide attention, Scope3 emissions, Kantar brand lift, retail-media partner metrics). Distinct from `metric_optimization` (seller-native metrics with no vendor binding) and from `reporting_capabilities.vendor_metrics` (which declares what the product can *report* rather than what it can *optimize against*). A product may report a vendor metric without being able to optimize for it. Buyers MUST verify the goal's `(vendor, metric_id)` is in `supported_metrics` AND that the package's `committed_metrics[]` includes a matching `{ scope: 'vendor', vendor, metric_id }` entry — optimization without committed reporting is unverifiable and is rejected at the wire level.", - title='Vendor Metric Optimization', - ), - ] = None - max_optimization_goals: Annotated[ - int | None, - Field( - description='Maximum number of optimization_goals this product accepts on a package. When absent, no limit is declared. Most social platforms accept only 1 goal — buyers sending arrays longer than this value should expect the seller to use only the highest-priority (lowest priority number) goal.', - ge=1, - ), - ] = None - measurement_readiness: Annotated[ - MeasurementReadiness3 | None, - Field( - description="Assessment of whether the buyer's event source setup is sufficient for this product to optimize effectively. Only present when the seller can evaluate the buyer's account context. Buyers should check this before creating media buys with event-based optimization goals.", - title='Measurement Readiness', - ), - ] = None - conversion_tracking: Annotated[ - ConversionTracking5 | None, - Field( - description="Conversion event tracking for this product. Presence indicates the product supports optimization_goals with kind: 'event'. Seller-level capabilities (supported event types, UID types, attribution windows) are declared in get_adcp_capabilities." - ), - ] = None - catalog_match: Annotated[ - CatalogMatch3 | None, - Field( - description='When the buyer provides a catalog on get_products, indicates which catalog items are eligible for this product. Only present for products where catalog matching is relevant (e.g., sponsored product listings, job boards, hotel ads).' - ), - ] = None - brief_relevance: Annotated[ - str | None, - Field( - description='Explanation of why this product matches the brief (only included when brief is provided)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='Expiration timestamp. After this time, the product may no longer be available for purchase and create_media_buy may reject packages referencing it.' - ), - ] = None - product_card: Annotated[ - ProductCard3 | None, - Field( - description='Optional standard visual card for displaying this product in user interfaces (catalog browsers, dashboards, agent UIs). Distinct from `format` — product_card describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection. Receivers render the card directly from these fields.' - ), - ] = None - product_card_detailed: Annotated[ - ProductCardDetailed3 | None, - Field( - description='Optional detailed card with hero + carousel + structured specifications, for rich product presentation (media-kit-style pages, full product detail views). Distinct from `format` — describes the UI rendering of the product itself, not the ad creative the product accepts. Typed inline; no format_id indirection.' - ), - ] = None - collections: Annotated[ - list[Collection] | None, - Field( - description='Collections available in this product. Each entry references collections declared in an adagents.json by domain and collection ID. Buyers resolve full collection objects from the referenced adagents.json.', - min_length=1, - ), - ] = None - collection_targeting_allowed: Annotated[ - bool | None, - Field( - description="Whether buyers can target a subset of this product's collections. When false (default), the product is a bundle — buyers get all listed collections. When true, buyers can select specific collections in the media buy." - ), - ] = False - installments: Annotated[ - list[Installment3] | None, - Field( - description='Specific installments included in this product. Each installment references its parent collection via collection_id when the product spans multiple collections. When absent with collections present, the product covers the collections broadly (run-of-collection).' - ), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field( - description='Registry policy IDs the seller enforces for this product. Enforcement level comes from the policy registry. Buyers can filter products by required policies.' - ), - ] = None - trusted_match: Annotated[ - TrustedMatch5 | None, - Field( - description='Trusted Match Protocol capabilities for this product. When present, the product supports real-time contextual and/or identity matching via TMP. Buyers use this to determine what response types the publisher can accept and whether brands can be selected dynamically at match time.' - ), - ] = None - material_submission: Annotated[ - MaterialSubmission | None, - Field( - description="Instructions for submitting physical creative materials (print, static OOH, cinema). Present only for products requiring physical delivery outside the digital creative assignment flow. Buyer agents MUST validate url and email domains against the seller's known domains (from adagents.json) before submitting materials. Never auto-submit without human confirmation." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - reason: Annotated[ - Reason | None, Field(description='Reason code indicating why input is needed') - ] = None - partial_results: Annotated[ - list[PartialResults | PartialResults1] | None, - Field(description='Partial product results that may help inform the clarification'), - ] = None - suggestions: Annotated[ - list[str] | None, Field(description='Suggested values or options for the required input') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig16(PushNotificationConfig): - pass - - -class Dimensions44(Dimensions8): - pass - - -class Dimensions46(Dimensions10): - pass - - -class Dimensions47(Dimensions11): - pass - - -class Dimensions48(Dimensions12): - pass - - -class EmbeddedProvenanceItem263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent526 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent527 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction273(Jurisdiction229): - pass - - -class Disclosure264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction273] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy263 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem263] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark263] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure264 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem263] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance263 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride57(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo57 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor38(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride57 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor38 | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent528 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent529 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction274(Jurisdiction229): - pass - - -class Disclosure265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction274] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy264 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem264] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark264] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure265 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem264] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance264 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride58(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo58 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor39(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride58 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue10(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor39, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[ - Dimensions44 | Dimensions45 | Dimensions46 | Dimensions47 | Dimensions48 | Dimensions49 - ], - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] - metrics: Annotated[ - Metrics8, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability12 | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue10] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class CoverageForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - points: Annotated[ - list[Point7], - Field( - description='Coverage or availability points. Each point reuses the standard ForecastPoint shape, MUST include a signal dimension, and MUST include metrics.coverage_rate. Use metrics.impressions for count denominators and metrics.coverage_rate for the fraction of the declared scope represented by the point.', - min_length=1, - ), - ] - forecast_range_unit: Annotated[ - Literal['availability'], - Field( - description="How to interpret the points array. Signal coverage forecasts always use 'availability' because the points describe available inventory or population coverage, not spend curves or temporal pacing." - ), - ] = 'availability' - method: ForecastMethod - scope: Annotated[ - Scope119, - Field( - description='Explicit denominator for the coverage forecast. This identifies the inventory, product, account, or custom universe that coverage_rate values are relative to. Additional seller-specific qualifiers are allowed for scopes such as line item type, ad server, inventory class, country, or flight window.' - ), - ] - bucket_semantics: Annotated[ - BucketSemantics, - Field( - description="'exclusive' means the returned signal-value buckets do not overlap with each other. 'overlapping' means one impression or user can appear in multiple returned buckets, so coverage_rate values may sum above 1.0. This field describes overlap among returned buckets; bucket_completeness declares whether the returned buckets cover the full denominator." - ), - ] - bucket_completeness: Annotated[ - BucketCompleteness, - Field( - description="'complete' means the returned buckets cover the declared denominator. For complete + exclusive forecasts, count metrics and coverage_rate values can be treated as a full partition, subject to metric additivity rules. 'partial' means omitted denominator share represents undisclosed, other, or unsupported buckets; buyers MUST NOT infer totals by summing returned points." - ), - ] - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast was computed.') - ] = None - valid_until: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast expires.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Signal2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_id: Annotated[ - SignalId | SignalId13 | SignalId | SignalId13 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - signal_ref: Annotated[ - SignalRef | SignalRef11 | SignalRef12 | SignalRef | SignalRef11 | SignalRef12 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque resolved-segment handle issued by this signal source. Pass this string verbatim to activate_signal.signal_agent_segment_id, and echo it in package signal targeting when the selected product option exposes the same handle. Treat the value as provider-scoped and opaque: providers MAY namespace it so two providers can expose similarly named signals without relying on a shared taxonomy. Do not pass the signal_id object as this handle, and do not reconstruct a segment handle from categorical values when get_signals returned a resolved segment.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] - description: Annotated[ - str, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_type: Annotated[ - SignalType, - Field( - description='Commercial/provenance type of signal (marketplace, custom, owned)', - title='Signal Availability Type', - ), - ] - data_provider: Annotated[ - str | None, - Field( - description='Human-readable source name for the signal, when applicable. For data_provider-scoped signals this is the data provider name; for signal_source-scoped signals it may identify the signal source or proprietary origin.' - ), - ] = None - coverage_percentage: Annotated[ - float | None, - Field( - deprecated=True, - description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only. If coverage_forecast includes an absent bucket over the same denominator, coverage_percentage SHOULD align with 100 * (1 - absent coverage_rate.mid).', - ge=0.0, - le=100.0, - ), - ] = None - coverage_forecast: Annotated[ - CoverageForecast | None, - Field( - description='Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal.', - title='Signal Coverage Forecast', - ), - ] = None - deployments: Annotated[list[Deployments3], Field(description='Array of deployment targets')] - pricing_options: Annotated[ - list[PricingOption18] | None, - Field( - description='Pricing options available for this signal when it has an incremental price. The buyer selects one and passes its pricing_option_id in report_usage or package-level signal_targeting_groups for billing verification. Omit when pricing is unavailable to the caller, bundled into the destination product, or has no incremental cost.', - min_length=1, - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - restricted_attributes: Annotated[ - list[RestrictedAttribute] | None, - Field(description='Restricted attribute categories this signal touches.', min_length=1), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field(description='Policy categories this signal is sensitive for.', min_length=1), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' - ), - ] = None - segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None - criteria_url: AnyUrl | None = None - data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None - methodology: Methodology | None = None - audience_expansion: bool | None = None - device_expansion: bool | None = None - refresh_cadence: RefreshCadence | None = None - lookback_window: RefreshCadence | None = None - onboarder: Onboarder | None = None - countries: Annotated[list[Country] | None, Field(min_length=1)] = None - consent_basis: Annotated[ - list[ConsentBasi] | None, - Field( - description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." - ), - ] = None - modeling: Modeling | None = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - dts_compliant_version: str | None = None - - -class Result267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError14 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig16 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - signals: Annotated[list[Signal2] | None, Field(description='Array of matching signals')] = None - errors: Annotated[ - list[Error8] | None, - Field( - description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem4] | None, - Field( - description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = None - unchanged: Annotated[ - Literal[True], - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals." - ), - ] = True - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig17(PushNotificationConfig): - pass - - -class EmbeddedProvenanceItem265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent530 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent531 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction276(Jurisdiction229): - pass - - -class Disclosure267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction276] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy265 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem265] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark265] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure267 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem265] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance265 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride59(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo59 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride59 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class ReportingBucket(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - protocol: CloudStorageProtocol - bucket: Annotated[ - str, - Field( - description='Bucket or container name', - max_length=63, - min_length=3, - pattern='^[a-z0-9][a-z0-9.-]{1,61}[a-z0-9]$', - ), - ] - prefix: Annotated[ - str | None, - Field( - description='Path prefix within the bucket. Seller appends date-based partitioning beneath this prefix.', - examples=['accounts/pinnacle/adcp', 'reporting/2024'], - max_length=512, - pattern='^[a-zA-Z0-9/_.-]+$', - ), - ] = None - region: Annotated[ - str | None, - Field( - description='Cloud region for the bucket', - examples=['us-east-1', 'europe-west1'], - max_length=64, - pattern='^[a-z0-9-]+$', - ), - ] = None - format: Annotated[ - Format | None, - Field( - description='File format for delivered files. Parquet, Avro, and ORC use internal compression (the top-level compression field is ignored for these formats).' - ), - ] = Format.jsonl - compression: Annotated[ - Compression | None, Field(description='Compression applied to delivered files') - ] = Compression.gzip - file_retention_days: Annotated[ - int, - Field( - description='How long reporting files are retained in the bucket before deletion. Buyers must read files within this window. Minimum recommended: 14 days.', - examples=[14, 30, 90], - ge=1, - ), - ] - setup_instructions: Annotated[ - AnyUrl | None, - Field( - description='URL to documentation for configuring buyer read access to this bucket (IAM role, service account, etc.). Operator-facing documentation — buyer agents MUST NOT auto-fetch this URL; surface it to a human operator. If an implementation fetches it (for preview), apply webhook URL SSRF validation and do not pass the fetched content into an LLM context without indirect-prompt-injection guarding. See docs/media-buy/media-buys/optimization-reporting#security-considerations-for-offline-delivery.' - ), - ] = None - - -class Authentication18(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[list[AuthenticationScheme], Field(max_length=1, min_length=1)] - credentials: Annotated[ - str | None, - Field( - description='Credentials for the legacy scheme. Bearer: token. HMAC-SHA256: shared secret. Minimum 32 characters. Exchanged out-of-band during onboarding. Write-only.', - min_length=32, - ), - ] = None - - -class NotificationConfig(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication18 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: AccountStatus - brand: Annotated[ - Brand | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: BillingParty | None = None - billing_entity: Annotated[ - BillingEntity | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: PaymentTerms | None = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: AccountScope | None = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AvailableAction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - action: MediaBuyValidAction - mode: MediaBuyActionMode - sla: Annotated[ - Sla | None, - Field( - description='Optional SLA commitment for this action on this buy. Absence means no commitment, not zero commitment.', - title='SLA Window', - ), - ] = None - terms_ref: Annotated[ - str | None, - Field( - description='Optional pointer into buy-terms negotiation (forward-references the buy-terms namespace landing via separate RFC). Schema accepts any string for now and will tighten to a structured reference when the buy-terms RFC ships.' - ), - ] = None - - -class PriceBreakdown37(PriceBreakdown): - pass - - -class Catalog1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class GeoMetro(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoMetrosExcludeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: MetroAreaSystem - values: Annotated[ - list[str], - Field( - description="Metro codes to exclude within the system (e.g., ['501', '602'] for Nielsen DMAs)", - min_length=1, - ), - ] - - -class GeoPostalAreas111(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - system: System3 - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas112(GeoPostalAreas11): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas113(GeoPostalAreas12): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas114(GeoPostalAreas13): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas115(GeoPostalAreas14): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas116(GeoPostalAreas15): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas117(GeoPostalAreas16): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas118(GeoPostalAreas17): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas119(GeoPostalAreas18): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas120(GeoPostalAreas19): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas121(GeoPostalAreas110): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas( - RootModel[ - GeoPostalAreas112 - | GeoPostalAreas113 - | GeoPostalAreas114 - | GeoPostalAreas115 - | GeoPostalAreas116 - | GeoPostalAreas117 - | GeoPostalAreas118 - | GeoPostalAreas119 - | GeoPostalAreas120 - | GeoPostalAreas121 - ] -): - root: Annotated[ - GeoPostalAreas112 - | GeoPostalAreas113 - | GeoPostalAreas114 - | GeoPostalAreas115 - | GeoPostalAreas116 - | GeoPostalAreas117 - | GeoPostalAreas118 - | GeoPostalAreas119 - | GeoPostalAreas120 - | GeoPostalAreas121, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - system: LegacyPostalCodeSystem - values: Annotated[ - list[str], Field(description='Postal codes within the legacy system.', min_length=1) - ] - - -class GeoPostalAreasExclude11(GeoPostalAreas111): - pass - - -class GeoPostalAreasExclude12(GeoPostalAreasExclude1): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude13(GeoPostalAreasExclude2): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude14(GeoPostalAreasExclude3): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude15(GeoPostalAreasExclude4): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude16(GeoPostalAreasExclude5): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude17(GeoPostalAreasExclude6): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude18(GeoPostalAreasExclude7): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude19(GeoPostalAreasExclude8): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude20(GeoPostalAreasExclude9): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude21(GeoPostalAreasExclude10): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude( - RootModel[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21 - ] -): - root: Annotated[ - GeoPostalAreasExclude12 - | GeoPostalAreasExclude13 - | GeoPostalAreasExclude14 - | GeoPostalAreasExclude15 - | GeoPostalAreasExclude16 - | GeoPostalAreasExclude17 - | GeoPostalAreasExclude18 - | GeoPostalAreasExclude19 - | GeoPostalAreasExclude20 - | GeoPostalAreasExclude21, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude22(GeoPostalAreas2): - pass - - -class FrequencyCap(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window5 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class AgeRestriction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[int, Field(description='Minimum age required', ge=13, le=99)] - verification_required: Annotated[ - bool | None, - Field(description='Whether verified age (not inferred) is required for compliance'), - ] = False - accepted_methods: Annotated[ - list[AgeVerificationMethod] | None, - Field( - description='Accepted verification methods. If omitted, any method the platform supports is acceptable.', - min_length=1, - ), - ] = None - - -class TravelTime(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Travel time limit.', ge=1.0)] - unit: TravelTimeUnit - - -class Radius(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: Annotated[float, Field(description='Radius distance.', gt=0.0)] - unit: DistanceUnit - - -class GeoProximityItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class KeywordTarget(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to target', min_length=1)] - match_type: MatchType - bid_price: Annotated[ - float | None, - Field( - description="Per-keyword bid price, denominated in the same currency as the package's pricing option. Overrides the package-level bid_price for this keyword. Inherits the max_bid interpretation from the pricing option: when max_bid is true, this is the keyword's bid ceiling; when false, this is the exact bid. If omitted, the package bid_price applies.", - ge=0.0, - ), - ] = None - - -class NegativeKeyword(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - keyword: Annotated[str, Field(description='The keyword to exclude', min_length=1)] - match_type: MatchType - - -class TargetingOverlay(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - Sequence[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - Sequence[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - Sequence[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas | GeoPostalAreas2] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - Sequence[GeoPostalAreasExclude | GeoPostalAreasExclude22] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting1] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent532 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent533 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction277(Jurisdiction229): - pass - - -class Disclosure268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction277] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy266 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem266] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark266] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure268 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem266] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance266 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride60(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo60 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor40(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride60 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor40, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement4 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent534 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent535 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction278(Jurisdiction229): - pass - - -class Disclosure269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction278] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy267 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem267] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark267] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure269 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem267] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance267 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride61(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo61 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor41(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride61 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor41, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow | None, - Field( - description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class CommittedMetrics1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier | None, - Field( - description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' - ), - ] - - -class EmbeddedProvenanceItem268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent536 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent537 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction279(Jurisdiction229): - pass - - -class Disclosure270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction279] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy268 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem268] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark268] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure270 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem268] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance268 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride62(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo62 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor42(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride62 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor42, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this vendor metric became part of the contract.' - ), - ] - - -class CommittedMetrics(RootModel[CommittedMetrics1 | CommittedMetrics2]): - root: Annotated[ - CommittedMetrics1 | CommittedMetrics2, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class OptimizationGoals1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - SupportedMetric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target18 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EventSource(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_source_id: Annotated[ - str, - Field( - description='Event source to include (must be configured on this account via sync_event_sources)', - min_length=1, - ), - ] - event_type: EventType - custom_event_name: Annotated[ - str | None, - Field( - description="Required when event_type is 'custom'. Platform-specific name for the custom event." - ), - ] = None - value_field: Annotated[ - str | None, - Field( - description="Which field in the event's custom_data carries the monetary value. The seller must use this field for value extraction and aggregation when computing ROAS and conversion value metrics. Required on at least one entry when target.kind is 'per_ad_spend' or 'maximize_value' — sellers must reject these target kinds when no event source entry includes value_field. When present without a value-oriented target, the seller may use it for delivery reporting (conversion_value, roas) but must not change the optimization objective. Common values: 'value', 'order_total', 'profit_margin'. This is not passed as a parameter to underlying platform APIs — the seller maps it to their platform's value ingestion mechanism." - ), - ] = None - value_factor: Annotated[ - float | None, - Field( - description="Multiplier the seller must apply to value_field before aggregation. Use -1 for refund events (negate the value), 0.01 for values in cents, -0.01 for refunds in cents. A value of 0 zeroes out this source's value contribution (the source still counts for event dedup). Defaults to 1. This is not passed as a parameter to underlying platform APIs — the seller applies it when computing aggregated value metrics." - ), - ] = 1 - - -class AttributionWindow5(AdCPBaseModel): - post_click: Annotated[ - PostClick | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target19 | Target20 | Target21 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow5 | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent538 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent539 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction280(Jurisdiction229): - pass - - -class Disclosure271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction280] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy269 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem269] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark269] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure271 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem269] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance269 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride63(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo63 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor43(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride63 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor43, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target22 | Target23 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals(RootModel[OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3]): - root: Annotated[ - OptimizationGoals1 | OptimizationGoals2 | OptimizationGoals3, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Cancellation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - canceled_at: Annotated[ - AwareDatetime, Field(description='ISO 8601 timestamp when this package was canceled.') - ] - canceled_by: CanceledBy - reason: Annotated[ - str | None, Field(description='Reason the package was canceled.', max_length=500) - ] = None - acknowledged_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the seller acknowledged the cancellation. Confirms inventory has been released and billing stopped. Absent until the seller processes the cancellation.' - ), - ] = None - - -class Package(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] - product_id: Annotated[ - str | None, - Field( - description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget allocation for this package in the currency specified by the pricing option', - ge=0.0, - ), - ] = None - pacing: Pacing | None = None - pricing_option_id: Annotated[ - str | None, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] = None - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown37 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - catalogs: Annotated[ - list[Catalog1] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms4 | None, - Field( - description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard4] | None, - Field( - description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics] | None, - Field( - description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. The unified shape is symmetric with `missing_metrics` and `aggregated_totals.metric_aggregates` — same atomic unit `(scope, metric_id, qualifier)` across contract, diff, and delivery, so reconciliation collapses to a row-level join on the tuple. Replaces the parallel-array design that shipped briefly in #3510.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'impressions', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'spend', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'completed_views', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'committed_at': '2026-05-30T14:22:00Z', - }, - ] - ], - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment] | None, - Field(description='Creative assets assigned to this package'), - ] = None - format_ids_to_provide: Annotated[ - list[FormatIdsToProvideItem] | None, - Field(description='Format IDs that creative assets will be provided for this package'), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - canceled: Annotated[ - bool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' - ), - ] = False - cancellation: Annotated[ - Cancellation | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", - max_length=100, - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class FrequencyCap2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress1 | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window7 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class PlannedDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo: Annotated[Geo | None, Field(description='Geographic targeting the seller will apply.')] = ( - None - ) - channels: Annotated[ - list[MediaChannel] | None, Field(description='Channels the seller will deliver on.') - ] = None - start_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight start the seller will use.') - ] = None - end_time: Annotated[ - AwareDatetime | None, Field(description='Actual flight end the seller will use.') - ] = None - frequency_cap: Annotated[ - FrequencyCap2 | None, - Field(description='Frequency cap the seller will apply.', title='Frequency Cap'), - ] = None - audience_summary: Annotated[ - str | None, - Field(description='Human-readable summary of the audience the seller will target.'), - ] = None - audience_targeting: Annotated[ - list[AudienceTargeting | AudienceTargeting3 | AudienceTargeting4 | AudienceTargeting5] - | None, - Field( - description='Structured audience targeting the seller will activate. Each entry is either a signal reference or a descriptive criterion. When present, governance agents MUST use this for bias/fairness validation and SHOULD ignore audience_summary for validation purposes. The audience_summary field is a human-readable rendering of this array, not an independent declaration.', - min_length=1, - ), - ] = None - total_budget: Annotated[ - float | None, Field(description='Total budget the seller will deliver against.', ge=0.0) - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for the budget.', pattern='^[A-Z]{3}$'), - ] = None - enforced_policies: Annotated[ - list[str] | None, - Field(description='Registry policy IDs the seller will enforce for this delivery.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError15 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig17 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - media_buy_id: Annotated[ - str, Field(description="Seller's unique identifier for the created media buy") - ] - account: Annotated[ - Account | None, - Field( - description='Account billed for this media buy. Includes advertiser, billing proxy (if any), and rate card applied.', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient | None, - Field( - description='Per-buy invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - media_buy_status: MediaBuyStatus | None = None - confirmed_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when this media buy was committed by the seller. Stable after it is set; do not update on later pause/resume/status/reporting transitions. May be null in deferred or manual-approval flows until seller commitment occurs.' - ), - ] - creative_deadline: Annotated[ - AwareDatetime | None, Field(description='ISO 8601 timestamp for creative upload deadline') - ] = None - revision: Annotated[ - int, - Field( - description='Initial revision number for this media buy. Use in subsequent update_media_buy requests intended to change state for optimistic concurrency.', - ge=1, - ), - ] - currency: Annotated[ - str | None, - Field( - description="ISO 4217 currency code (e.g., USD, EUR, GBP) for monetary values at this media buy level. total_budget is denominated in this currency. Package-level fields may override with package.currency. In proposal mode the seller derives this from the request's total_budget object (total_budget.currency); in manual mode it is present when all packages share a currency. Matches the currency field in subsequent get_media_buys responses.", - pattern='^[A-Z]{3}$', - ), - ] = None - total_budget: Annotated[ - float | None, - Field( - description='Total budget amount across all packages, denominated in currency. Note: the create_media_buy request encodes total_budget as an object {amount, currency} (proposal mode only); this response field is the flattened scalar amount, with currency promoted to the sibling currency field. Present when the seller can compute a deterministic aggregate — always in proposal mode, conditionally in manual mode when all packages share a currency. Matches the total_budget field in subsequent get_media_buys responses.', - ge=0.0, - ), - ] = None - valid_actions: Annotated[ - list[MediaBuyValidAction] | None, - Field( - description='Flat-vocabulary actions the buyer can perform on this media buy after creation. Saves a round-trip to get_media_buys. Deprecated in favor of `available_actions[]`, which carries `mode`, optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.' - ), - ] = None - available_actions: Annotated[ - list[AvailableAction] | None, - Field( - description='Structured per-buy resolution of actions available immediately after creation. Authoritative — see `get-media-buys-response.json` for full semantics.' - ), - ] = None - packages: Annotated[ - list[Package], - Field(description='Array of created packages with complete state information'), - ] - planned_delivery: Annotated[ - PlannedDelivery | None, - Field( - description="The seller's interpreted delivery parameters. Describes what the seller will actually run -- geo, channels, flight dates, frequency caps, and budget. Present when the account has governance_agents or when the seller chooses to provide delivery transparency.", - title='Planned Delivery', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Authentication19(Authentication): - pass - - -class PushNotificationConfig18(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication19 | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Result278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError16 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig18 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error10], - Field(description='Array of errors explaining why the operation failed', min_length=1), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig19(PushNotificationConfig18): - pass - - -class Result279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError17 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig19 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error11] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig20(PushNotificationConfig18): - pass - - -class PriceBreakdown38(PriceBreakdown): - pass - - -class Catalog2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping9] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - - -class GeoPostalAreas311(GeoPostalAreas111): - pass - - -class GeoPostalAreas312(GeoPostalAreas31): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas313(GeoPostalAreas32): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas314(GeoPostalAreas33): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas315(GeoPostalAreas34): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas316(GeoPostalAreas35): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas317(GeoPostalAreas36): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas318(GeoPostalAreas37): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas319(GeoPostalAreas38): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas320(GeoPostalAreas39): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas321(GeoPostalAreas310): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreas3( - RootModel[ - GeoPostalAreas312 - | GeoPostalAreas313 - | GeoPostalAreas314 - | GeoPostalAreas315 - | GeoPostalAreas316 - | GeoPostalAreas317 - | GeoPostalAreas318 - | GeoPostalAreas319 - | GeoPostalAreas320 - | GeoPostalAreas321 - ] -): - root: Annotated[ - GeoPostalAreas312 - | GeoPostalAreas313 - | GeoPostalAreas314 - | GeoPostalAreas315 - | GeoPostalAreas316 - | GeoPostalAreas317 - | GeoPostalAreas318 - | GeoPostalAreas319 - | GeoPostalAreas320 - | GeoPostalAreas321, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreas4(GeoPostalAreas2): - pass - - -class GeoPostalAreasExclude2311(GeoPostalAreas111): - pass - - -class GeoPostalAreasExclude2312(GeoPostalAreasExclude231): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2313(GeoPostalAreasExclude232): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2314(GeoPostalAreasExclude233): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2315(GeoPostalAreasExclude234): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2316(GeoPostalAreasExclude235): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2317(GeoPostalAreasExclude236): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2318(GeoPostalAreasExclude237): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2319(GeoPostalAreasExclude238): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2320(GeoPostalAreasExclude239): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude2321(GeoPostalAreasExclude2310): - model_config = ConfigDict( - extra='forbid', - ) - values: Annotated[ - list[str], Field(description='Postal codes within the country and system.', min_length=1) - ] - - -class GeoPostalAreasExclude23( - RootModel[ - GeoPostalAreasExclude2312 - | GeoPostalAreasExclude2313 - | GeoPostalAreasExclude2314 - | GeoPostalAreasExclude2315 - | GeoPostalAreasExclude2316 - | GeoPostalAreasExclude2317 - | GeoPostalAreasExclude2318 - | GeoPostalAreasExclude2319 - | GeoPostalAreasExclude2320 - | GeoPostalAreasExclude2321 - ] -): - root: Annotated[ - GeoPostalAreasExclude2312 - | GeoPostalAreasExclude2313 - | GeoPostalAreasExclude2314 - | GeoPostalAreasExclude2315 - | GeoPostalAreasExclude2316 - | GeoPostalAreasExclude2317 - | GeoPostalAreasExclude2318 - | GeoPostalAreasExclude2319 - | GeoPostalAreasExclude2320 - | GeoPostalAreasExclude2321, - Field( - description='Postal area values. Prefer the native country + postal system form. Deprecated legacy country-fused postal-system tokens remain accepted for compatibility.', - title='PostalArea', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class GeoPostalAreasExclude24(GeoPostalAreas2): - pass - - -class FrequencyCap3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - suppress: Annotated[ - Suppress2 | None, - Field( - description='Cooldown period between consecutive exposures to the same entity. Prevents back-to-back ad delivery (e.g. {"interval": 60, "unit": "minutes"} for a 1-hour cooldown). Preferred over suppress_minutes.' - ), - ] = None - suppress_minutes: Annotated[ - float | None, - Field( - description='Deprecated — use suppress instead. Cooldown period in minutes between consecutive exposures to the same entity (e.g. 60 for a 1-hour cooldown).', - ge=0.0, - ), - ] = None - max_impressions: Annotated[ - int | None, - Field( - description="Maximum number of impressions per entity per window. For duration windows, implementations typically use a rolling window; 'campaign' applies a fixed cap across the full flight.", - ge=1, - ), - ] = None - per: Annotated[ - ReachUnit | None, - Field( - description='Entity granularity for impression counting. Required when max_impressions is set.' - ), - ] = None - window: Annotated[ - Window8 | None, - Field( - description='Time window for the max_impressions cap (e.g. {"interval": 7, "unit": "days"} or {"interval": 1, "unit": "campaign"} for the full flight). Required when max_impressions is set.' - ), - ] = None - - -class GeoProximityItem1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - lat: Annotated[ - float | None, - Field( - description='Latitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-90.0, - le=90.0, - ), - ] = None - lng: Annotated[ - float | None, - Field( - description='Longitude in decimal degrees (WGS 84). Required for travel_time and radius methods.', - ge=-180.0, - le=180.0, - ), - ] = None - label: Annotated[ - str | None, - Field( - description="Human-readable label for this entry (e.g., 'Düsseldorf', 'Heathrow Airport', 'Primary trade area')." - ), - ] = None - travel_time: Annotated[ - TravelTime | None, - Field( - description='Travel time limit for isochrone calculation. The platform resolves this to a geographic boundary based on actual transportation networks.' - ), - ] = None - transport_mode: TransportMode | None = None - radius: Annotated[ - Radius | None, - Field( - description='Simple radius from the point. The platform draws a circle of this distance around the coordinates.' - ), - ] = None - geometry: Annotated[ - Geometry2 | None, - Field( - description='Pre-computed GeoJSON geometry defining the proximity boundary. Use when the buyer has already calculated isochrones (via TravelTime, Mapbox, etc.) or has custom boundaries. When geometry is provided, lat/lng are not required.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class TargetingOverlay1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - geo_countries: Annotated[ - list[GeoCountry] | None, - Field( - description="Restrict delivery to specific countries. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_countries_exclude: Annotated[ - list[GeoCountriesExcludeItem] | None, - Field( - description="Exclude specific countries from delivery. ISO 3166-1 alpha-2 codes (e.g., 'US', 'GB', 'DE').", - min_length=1, - ), - ] = None - geo_regions: Annotated[ - list[GeoRegion] | None, - Field( - description="Restrict delivery to specific regions/states. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_regions_exclude: Annotated[ - list[GeoRegionsExcludeItem] | None, - Field( - description="Exclude specific regions/states from delivery. ISO 3166-2 subdivision codes (e.g., 'US-CA', 'GB-SCT').", - min_length=1, - ), - ] = None - geo_metros: Annotated[ - list[GeoMetro] | None, - Field( - description='Restrict delivery to specific metro areas. Each entry specifies the classification system and target values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_metros_exclude: Annotated[ - list[GeoMetrosExcludeItem] | None, - Field( - description='Exclude specific metro areas from delivery. Each entry specifies the classification system and excluded values. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas: Annotated[ - list[GeoPostalAreas3 | GeoPostalAreas4] | None, - Field( - description='Restrict delivery to specific postal areas. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - geo_postal_areas_exclude: Annotated[ - list[GeoPostalAreasExclude23 | GeoPostalAreasExclude24] | None, - Field( - description='Exclude specific postal areas from delivery. Prefer the native country + postal system form. The deprecated legacy country-fused postal-system tokens remain accepted for compatibility. Seller must declare supported systems in get_adcp_capabilities.', - min_length=1, - ), - ] = None - daypart_targets: Annotated[ - list[DaypartTarget] | None, - Field( - description='Restrict delivery to specific time windows. Each entry specifies days of week and an hour range.', - min_length=1, - ), - ] = None - axe_include_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to include for targeting.', - ), - ] = None - axe_exclude_segment: Annotated[ - str | None, - Field( - deprecated=True, - description='Deprecated: Use TMP provider fields instead. AXE segment ID to exclude from targeting.', - ), - ] = None - audience_include: Annotated[ - list[str] | None, - Field( - description='Restrict delivery to members of these first-party CRM audiences. Only users present in the uploaded lists are eligible. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Not for lookalike expansion — express that intent in the campaign brief. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - audience_exclude: Annotated[ - list[str] | None, - Field( - description='Suppress delivery to members of these first-party CRM audiences. Matched users are excluded regardless of other targeting. References audience_id values from sync_audiences on the same seller account — audience IDs are not portable across sellers. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - signal_targeting_groups: Annotated[ - SignalTargetingGroups1 | None, - Field( - description="Basic Boolean grouping for seller-offered signals. v1 supports a required top-level operator 'all' and child groups with operator 'any' for include groups or 'none' for exclusion groups. Example semantics: group 1 any(A, B) plus group 2 none(C, D) means (A OR B) AND NOT (C OR D). Signal entries reference named signal definitions with signal_ref scope 'product' for product-local signal options or scope 'data_provider' for external signals published in adagents.json signals[]. For simple include-only targeting, send one child group with operator 'any'. Sellers SHOULD reject entries that are not available for the product through inline signal_targeting_options or get_signals, are not active for the account, or exceed the product's signal_targeting_allowed/signal_targeting_rules/product terms. Signal targeting limits are product-scoped, not declared in get_adcp_capabilities, because products may be backed by different ad servers. Sellers MUST echo applied signal_targeting_groups on the resulting package state, including fixed/default selections. On update_media_buy, sellers MAY reject changes that require repricing with REQUOTE_REQUIRED.", - title='Package Signal Targeting Groups', - ), - ] = None - signal_targeting: Annotated[ - list[SignalTargeting5] | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_targeting_groups for package-level signal targeting. Legacy flat signal_targeting remains accepted during the SignalRef migration window but cannot express grouped include/exclude composition or product-scoped pricing.', - min_length=1, - ), - ] = None - frequency_cap: Annotated[ - FrequencyCap3 | None, - Field( - description='Frequency capping settings for package-level application. Two types of frequency control can be used independently or together: suppress enforces a cooldown between consecutive exposures; max_impressions + per + window caps total exposures per entity in a time window. When both suppress and max_impressions are set, an impression is delivered only if both constraints permit it (AND semantics). At least one of suppress, suppress_minutes, or max_impressions must be set.', - title='Frequency Cap', - ), - ] = None - property_list: Annotated[ - PropertyList | None, - Field( - description="Reference to a property list for targeting specific properties within this product. The package runs on the intersection of the product's publisher_properties and this list. Sellers SHOULD return a validation error if the product has property_targeting_allowed: false.", - title='Property List Reference', - ), - ] = None - collection_list: Annotated[ - CollectionList | None, - Field( - description='Reference to a collection list for including specific collections (programs, shows) within this product. The package runs on the intersection of matched collections and this list. Use for inclusion-based collection targeting. Seller must declare support in get_adcp_capabilities.', - title='Collection List Reference', - ), - ] = None - collection_list_exclude: Annotated[ - CollectionListExclude | None, - Field( - description="Reference to a collection list for excluding specific collections (programs, shows) from this product. Matched collections must not carry the buyer's ads. Use for brand safety do-not-air lists. Seller must declare support in get_adcp_capabilities.", - title='Collection List Reference', - ), - ] = None - age_restriction: Annotated[ - AgeRestriction | None, - Field( - description='Age restriction for compliance. Use for legal requirements (alcohol, gambling), not audience targeting.' - ), - ] = None - device_platform: Annotated[ - list[DevicePlatform] | None, - Field( - description='Restrict to specific platforms. Use for technical compatibility (app only works on iOS). Values from Sec-CH-UA-Platform standard, extended for CTV.', - min_length=1, - ), - ] = None - device_type: Annotated[ - list[DeviceType] | None, - Field( - description='Restrict to specific device form factors. Use for campaigns targeting hardware categories rather than operating systems (e.g., mobile-only promotions, CTV campaigns).', - min_length=1, - ), - ] = None - device_type_exclude: Annotated[ - list[DeviceType] | None, - Field( - description='Exclude specific device form factors from delivery (e.g., exclude CTV for app-install campaigns).', - min_length=1, - ), - ] = None - store_catchments: Annotated[ - list[StoreCatchment] | None, - Field( - description='Target users within store catchment areas from a synced store catalog. Each entry references a store-type catalog and optionally narrows to specific stores or catchment zones.', - min_length=1, - ), - ] = None - geo_proximity: Annotated[ - list[GeoProximityItem1] | None, - Field( - description='Target users within travel time, distance, or a custom boundary around arbitrary geographic points. Multiple entries use OR semantics — a user within range of any listed point is eligible. For campaigns targeting 10+ locations, consider using store_catchments with a location catalog instead. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - language: Annotated[ - list[LanguageItem] | None, - Field( - description="Restrict to users with specific language preferences. ISO 639-1 codes (e.g., 'en', 'es', 'fr').", - min_length=1, - ), - ] = None - keyword_targets: Annotated[ - list[KeywordTarget] | None, - Field( - description='Keyword targeting for search and retail media platforms. Restricts delivery to queries matching the specified keywords. Each keyword is identified by the tuple (keyword, match_type) — the same keyword string with different match types are distinct targets. Sellers SHOULD reject duplicate (keyword, match_type) pairs within a single request. Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - negative_keywords: Annotated[ - list[NegativeKeyword] | None, - Field( - description='Keywords to exclude from delivery. Queries matching these keywords will not trigger the ad. Each negative keyword is identified by the tuple (keyword, match_type). Seller must declare support in get_adcp_capabilities.', - min_length=1, - ), - ] = None - - -class EmbeddedProvenanceItem270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent540 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent541 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction281(Jurisdiction229): - pass - - -class Disclosure272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction281] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy270 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem270] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark270] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure272 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem270] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance270 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride64(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo64 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor44(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride64 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class BillingMeasurement5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor44, - Field( - description="Vendor whose measurement of the billing metric is authoritative for invoicing (e.g., { domain: 'campaignmanager.google.com' } for buyer's DCM, { domain: 'admanager.google.com' } for seller's GAM).", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - max_variance_percent: Annotated[ - float | None, - Field( - description="Maximum acceptable variance between the billing vendor's count and the other party's count before resolution is triggered (e.g., 10 means a 10% divergence triggers review).", - ge=0.0, - lt=100.0, - ), - ] = None - measurement_window: Annotated[ - str | None, - Field( - description="Which measurement maturation stage the billing metric is reconciled against. References a window_id from the product's reporting_capabilities.measurement_windows. Examples: 'c7' for broadcast TV guarantees (live + 7 days DVR), 'final' for DOOH after IVT/fraud-check processing, 'post_sivt' for digital after sophisticated invalid-traffic filtering, 'downloads_30d' for podcast. When absent, billing is based on the seller's standard reporting without windowed maturation.", - examples=[ - 'live', - 'c3', - 'c7', - 'tentative', - 'final', - 'post_ivt', - 'post_sivt', - 'downloads_30d', - ], - ), - ] = None - finalization_deadline_hours: Annotated[ - int | None, - Field( - description='Maximum hours by which the authoritative party MUST publish a final record (`is_final: true` / `finalized_at` on `get_media_buy_delivery`, or `final: true` / `finalized_at` on `report_usage`). **Anchor:** when `measurement_window` is set, hours are counted from the close of that window (e.g., 240h after `c7` close = ~10 days after the 7-day DVR accumulation completes); when `measurement_window` is absent, hours are counted from `reporting_period.end`. Picking a single anchor avoids ambiguity for windowed channels where `reporting_period.end` and window close differ by days. The deadline applies to whichever party is named in `vendor` — seller, buyer, or third-party vendor — symmetrically. When the deadline elapses without a final record, the counterparty MAY fall back to its own attestation for invoicing (seller falls back to seller-attested numbers via `get_media_buy_delivery`; buyer falls back to a buyer-attested `report_usage` push), and the breach is treated like any other measurement-terms breach under `makegood_policy`. Absent means no contractual deadline — finalization is best-effort and disagreements resolve out of band.', - ge=0, - ), - ] = None - - -class MeasurementTerms5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - billing_measurement: Annotated[ - BillingMeasurement5 | None, - Field( - description="Which vendor's count of the billing metric governs invoicing. The billing metric is determined by the pricing_model on the selected pricing_option (e.g., impressions for CPM, completed views for CPCV)." - ), - ] = None - makegood_policy: Annotated[ - MakegoodPolicy | None, - Field( - description='Remedies available when a performance standard or billing measurement variance is breached. Seller declares which remedy types they support. When a breach occurs, the seller proposes a remedy from this menu; the buyer accepts or disputes.' - ), - ] = None - - -class EmbeddedProvenanceItem271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent542 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent543 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction282(Jurisdiction229): - pass - - -class Disclosure273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction282] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy271 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem271] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark271] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure273 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem271] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance271 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride65(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo65 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor45(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride65 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class PerformanceStandard5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - metric: PerformanceStandardMetric - threshold: Annotated[ - float, - Field( - description='Rate threshold as a decimal (e.g., 0.70 for 70%). Whether this is a floor or ceiling depends on the metric: for viewability, completion_rate, brand_safety, attention_score the actual rate must be >= threshold; for ivt the actual rate must be <= threshold.', - ge=0.0, - le=1.0, - ), - ] - standard: ViewabilityStandard | None = None - vendor: Annotated[ - Vendor45, - Field( - description="Vendor measuring this metric (e.g., { domain: 'doubleverify.com' }). The vendor's brand.json agents array (type: 'measurement') is the discovery point for their measurement agent. When specified on a confirmed package, creatives MUST include tracker_script or tracker_pixel assets from this vendor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - - -class Qualifier3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - viewability_standard: ViewabilityStandard | None = None - completion_source: CompletionSource | None = None - attribution_methodology: AttributionMethodology | None = None - attribution_window: Annotated[ - AttributionWindow6 | None, - Field( - description="Time window over which outcome attribution is computed. **Object-valued, not string** — MUST be a structured duration object like `{interval: 14, unit: 'days'}`, NEVER a shorthand string like `'14d'`. SHOULD be set when `metric_id` is an outcome metric and the seller commits to a specific window. Common windows: `{interval: 7, unit: 'days'}`, `{interval: 14, unit: 'days'}`, `{interval: 30, unit: 'days'}`, `{interval: 90, unit: 'days'}`. Two outcome rows with the same `metric_id` and `attribution_methodology` but different `attribution_window` represent the same metric measured over different periods — the join on `(metric_id, qualifier)` keeps them as separate rows so buyers don't accidentally aggregate across windows.", - title='Duration', - ), - ] = None - lift_dimension: LiftDimension | None = None - - -class CommittedMetrics4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['standard'], - Field(description='Standard metric from the closed `available-metric.json` enum.'), - ] = 'standard' - metric_id: AvailableMetric - qualifier: Annotated[ - Qualifier3 | None, - Field( - description="Disambiguates metrics whose definition varies by qualifier. Today carries five keys — `viewability_standard` (MRC vs GroupM viewability), `completion_source` (seller- vs vendor-attested completion), `attribution_methodology` (how attribution was computed for outcome metrics), `attribution_window` (the time window over which outcomes were attributed), and `lift_dimension` (which dimension of brand_lift this row represents — awareness, consideration, etc.). Required when the underlying `metric_id` has multiple incompatible measurement paths AND the seller commits to a specific one. Symmetric on `missing_metrics`. Reserved for additive qualifiers in future minors — schema is closed (`additionalProperties: false`); new keys ship explicitly. **Heterogeneous value types**: qualifier values can be either string enums (`viewability_standard`, `completion_source`, `attribution_methodology`, `lift_dimension`) or structured objects (`attribution_window` is a duration `{interval, unit}`). Consumers MUST dispatch on key name to know value shape; structured-value qualifiers join on canonical (key-sorted) deep equality so `{interval: 14, unit: 'days'}` and `{unit: 'days', interval: 14}` resolve to the same partition. Rate-style metrics (`new_to_brand_rate`, `engagement_rate`, etc.) inherit the methodology of their numerator — when a rate carries `attribution_methodology` qualifier, it applies to the underlying conversions/events being rated." - ), - ] = None - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this metric became part of the contract. Day-1 commitments use `create_media_buy.confirmed_at`; mid-flight additions use the time the amendment was accepted.' - ), - ] - - -class EmbeddedProvenanceItem272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent544 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent545 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction283(Jurisdiction229): - pass - - -class Disclosure274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction283] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy272 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem272] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark272] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure274 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem272] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance272 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride66(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo66 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor46(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride66 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class CommittedMetrics5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Literal['vendor'], - Field(description='Vendor-defined metric, identified by the tuple `(vendor, metric_id)`.'), - ] = 'vendor' - vendor: Annotated[ - Vendor46, - Field( - description="Vendor that defines and computes this metric. The vendor's `brand.json` `agents[type='measurement']` is the canonical anchor.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - committed_at: Annotated[ - AwareDatetime, - Field( - description='ISO 8601 timestamp when this vendor metric became part of the contract.' - ), - ] - - -class CommittedMetrics3(RootModel[CommittedMetrics4 | CommittedMetrics5]): - root: Annotated[ - CommittedMetrics4 | CommittedMetrics5, - Field( - description="One metric in a package's binding reporting contract. Each entry uses `scope` as the discriminator and identifies a standard or vendor-defined metric that the seller has committed to populate in delivery reports.", - discriminator='scope', - title='Committed Metric', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class OptimizationGoals5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['metric'] = 'metric' - metric: Annotated[ - SupportedMetric, - Field( - description="Seller-native metric to optimize for. Delivery metrics: clicks (link clicks, swipe-throughs, CTA taps that navigate away), views (viewable impressions), completed_views (video/audio completions — see view_duration_seconds), reach (unique audience reach — see reach_unit and target_frequency). Duration/score metrics: viewed_seconds (time in view per impression — reported back via `delivery-metrics.viewability.viewed_seconds`, governed by the viewability `standard`). Audience action metrics: engagements (any direct interaction with the ad unit beyond viewing — social reactions/comments/shares, story/unit opens, interactive overlay taps, companion banner interactions on audio and CTV), follows (new followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes; paid subscriptions use event_type: subscribe), saves (saves, bookmarks, playlist adds, pins — signals of intent to return), profile_visits (visits to the brand's in-platform page — profile, artist page, channel, or storefront. Does not include external website clicks, which are covered by 'clicks'). **DEPRECATED values** (slated for removal at next major): `attention_seconds` and `attention_score` — these have no industry-graduated definition (DoubleVerify, IAS, Adelaide, TVision, Lumen each define them differently) and cannot be meaningfully optimized for without a vendor binding. Use `kind: 'vendor_metric'` with an explicit `vendor` and `metric_id` instead — that path binds the goal to a specific measurement vendor and reconciles to the same `(vendor, metric_id)` key in delivery's `vendor_metric_values[]`. Sellers MAY reject the deprecated values with `TERMS_REJECTED` and a suggestion to use the `vendor_metric` kind." - ), - ] - reach_unit: Annotated[ - ReachUnit | None, - Field( - description="Unit for reach measurement. Required when metric is 'reach'. Must be a value declared in the product's metric_optimization.supported_reach_units." - ), - ] = None - target_frequency: Annotated[ - TargetFrequency1 | None, - Field( - description="Target frequency band for reach optimization. Only applicable when metric is 'reach'. Frames frequency as an optimization signal: the seller should treat impressions toward entities already within the [min, max] band as lower-value, and impressions toward unreached entities as higher-value. This shifts budget toward fresh reach rather than re-reaching known users. When omitted, the seller maximizes unique reach without a frequency constraint. A hard cap can still be layered via targeting_overlay.frequency_cap if a ceiling is needed." - ), - ] = None - view_duration_seconds: Annotated[ - float | None, - Field( - description="Minimum video view duration in seconds that qualifies as a completed_view for this goal. Only applicable when metric is 'completed_views'. When omitted, the seller uses their platform default (typically 2–15 seconds). Common values: 2 (Snap/LinkedIn default), 6 (TikTok), 15 (Snap 15-second views, Meta ThruPlay). Sellers declare which durations they support in metric_optimization.supported_view_durations. Sellers must reject goals with unsupported values — silent rounding would create measurement discrepancies.", - gt=0.0, - ), - ] = None - target: Annotated[ - Target | Target18 | None, - Field( - description='Target for this metric. When omitted, the seller optimizes for maximum metric volume within budget.', - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class AttributionWindow7(AdCPBaseModel): - post_click: Annotated[ - PostClick1 | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView1 | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class OptimizationGoals6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['event'] = 'event' - event_sources: Annotated[ - list[EventSource], - Field( - description='Event source and type pairs that feed this goal. Each entry identifies a source and event type to include. When the seller supports multi_source_event_dedup (declared in get_adcp_capabilities), they deduplicate by event_id across all entries — the same business event from multiple sources counts once, using value_field and value_factor from the first matching entry. When multi_source_event_dedup is false or absent, buyers should use a single entry per goal; the seller will use only the first entry. All event sources must be configured via sync_event_sources.', - min_length=1, - ), - ] - target: Annotated[ - Target19 | Target20 | Target21 | None, - Field( - description='Target cost or return for this event goal. When omitted, the seller optimizes for maximum conversion count within budget — regardless of whether value_field is present on event sources. The presence of value_field alone does not change the optimization objective; it only makes value available for reporting. An explicit target of maximize_value or per_ad_spend is required to steer toward value.', - discriminator='kind', - ), - ] = None - attribution_window: Annotated[ - AttributionWindow7 | None, - Field( - description="Attribution window for this optimization goal — references the canonical `attribution-window` shape (post_click, post_view, model). Values must match an option declared in the seller's `conversion_tracking.attribution_windows` capability. Sellers MUST reject windows not in their declared capabilities. When the entire field is omitted, the seller uses their default window." - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class EmbeddedProvenanceItem273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent546 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent547 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction284(Jurisdiction229): - pass - - -class Disclosure275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction284] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy273 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem273] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark273] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure275 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem273] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance273 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride67(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo67 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor47(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride67 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class OptimizationGoals7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Literal['vendor_metric'] = 'vendor_metric' - vendor: Annotated[ - Vendor47, - Field( - description='Vendor that defines and computes this metric. Same shape as `vendor_metric_values.vendor`, `reporting_capabilities.vendor_metrics[].vendor`, and `vendor_metric_optimization.supported_metrics[].vendor` — symmetric across discovery, capability, commitment, optimization, and reporting surfaces.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary (e.g., `attention_score`, `attention_seconds`, `gco2e_per_impression`, `awareness_lift`). MUST be present in the vendor's published `measurement.metrics[]` catalog and in the product's `vendor_metric_optimization.supported_metrics[]`.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - target: Annotated[ - Target22 | Target23 | None, - Field( - description="Target for this vendor metric. When omitted, the seller optimizes for maximum metric volume / score within budget. `cost_per` and `threshold_rate` semantics mirror the same target kinds on the `metric` kind — units are vendor-defined and depend on the vendor's `measurement.metrics[]` declaration for this `metric_id`.", - discriminator='kind', - ), - ] = None - priority: Annotated[ - int | None, - Field( - description='Relative priority among all optimization goals on this package. 1 = highest priority (primary goal); higher numbers are lower priority (secondary signals). When omitted, sellers may use array position as priority.', - ge=1, - ), - ] = None - - -class OptimizationGoals4(RootModel[OptimizationGoals5 | OptimizationGoals6 | OptimizationGoals7]): - root: Annotated[ - OptimizationGoals5 | OptimizationGoals6 | OptimizationGoals7, - Field( - description='A single optimization target for a package. Packages accept an array of optimization_goals. When multiple goals are present, priority determines which the seller focuses on — 1 is highest priority (primary goal); higher numbers are secondary. When priorities are present but no goal is priority 1, the goal with the lowest priority value is primary (e.g., priorities of 2 and 3 mean 2 is primary). Duplicate priority values result in undefined seller behavior.', - discriminator='kind', - title='Optimization Goal', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class AffectedPackage(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - package_id: Annotated[str, Field(description="Seller's unique identifier for the package")] - product_id: Annotated[ - str | None, - Field( - description="ID of the product this package is based on. For packages created from an explicit create_media_buy package request, sellers MUST echo the request package's product_id on every response package object that represents that requested package." - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget allocation for this package in the currency specified by the pricing option', - ge=0.0, - ), - ] = None - pacing: Pacing | None = None - pricing_option_id: Annotated[ - str | None, - Field( - description="ID of the selected pricing option from the product's pricing_options array" - ), - ] = None - bid_price: Annotated[ - float | None, - Field( - description="Bid price for auction-based pricing. This is the exact bid/price to honor unless the selected pricing option has max_bid=true, in which case bid_price is the buyer's maximum willingness to pay (ceiling).", - ge=0.0, - ), - ] = None - price_breakdown: Annotated[ - PriceBreakdown38 | None, - Field( - description="Breaks down the composition of fixed_price from a list (rate card) price through adjustments. Adjustments fall into four kinds: fees (increase buyer price), discounts (reduce buyer price), commissions (revenue splits that don't affect buyer price), and settlement terms (applied at invoicing). The invariant is: list_price with all fee and discount adjustments applied sequentially equals fixed_price. Fees increase the running price; discounts reduce it. This invariant applies only when fixed_price is present on the parent object; on auction-based packages the breakdown is informational only. All monetary values are rounded to currency precision at each step. Budgets are always denominated at the fixed_price level, inclusive of commissions.", - title='Price Breakdown', - ), - ] = None - impressions: Annotated[ - float | None, Field(description='Impression goal for this package', ge=0.0) - ] = None - catalogs: Annotated[ - list[Catalog2] | None, - Field( - description='Catalogs this package promotes. Each catalog MUST have a distinct type (e.g., one product catalog, one store catalog). This constraint is enforced at the application level — sellers MUST reject requests containing multiple catalogs of the same type with a validation_error. Echoed from the create_media_buy request.' - ), - ] = None - format_ids: Annotated[ - list[FormatId] | None, - Field( - description='Legacy named-format IDs supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it, including dual-emission cases where `format_option_refs` was the winning selector, so read surfaces preserve the original wire contract. Omitted means the request did not carry legacy format_ids unless the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - format_option_refs: Annotated[ - list[FormatOptionRefs3] | None, - Field( - description='Structured 3.1+ format option references supplied for this package on create_media_buy. Sellers SHOULD echo this field whenever the request included it. Publisher-catalog-backed options are identified by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options are identified by `{ scope: "product", format_option_id }` and resolve only against this package\'s target product. Omitted means the request did not carry format_option_refs unless the seller cannot reconstruct legacy requests created before this field was persisted.', - min_length=1, - ), - ] = None - format_kind: CanonicalFormatKind | None = None - params: Annotated[ - dict[str, Any] | None, - Field( - description='Parameters for the direct canonical selector in `format_kind`, echoed from the create_media_buy request whenever the request included it. Requires `format_kind`; omitted only when the request did not carry direct canonical params or when the seller cannot reconstruct legacy requests created before this field was persisted.' - ), - ] = None - targeting_overlay: Annotated[ - TargetingOverlay1 | None, - Field( - description='Optional restriction overlays for media buys. Most targeting should be expressed in the brief and handled by the publisher. These fields are for functional restrictions: geographic (RCT testing, regulatory compliance, proximity targeting), age verification (alcohol, gambling), device platform (app compatibility), language (localization), and keyword targeting (search/retail media).', - title='Targeting Overlay', - ), - ] = None - measurement_terms: Annotated[ - MeasurementTerms5 | None, - Field( - description="Agreed billing measurement and makegood terms for this package. Reflects what was negotiated — may differ from the buyer's proposal or the product's defaults. When present, these terms are binding for the package's duration.", - title='Measurement Terms', - ), - ] = None - performance_standards: Annotated[ - list[PerformanceStandard5] | None, - Field( - description='Agreed performance standards for this package. When any entry specifies a vendor, creatives assigned to this package MUST include corresponding tracker_script or tracker_pixel assets from that vendor.', - min_length=1, - ), - ] = None - committed_metrics: Annotated[ - list[CommittedMetrics3] | None, - Field( - description="The binding reporting contract for this package — what the seller has agreed to populate in delivery reports. Each entry carries an explicit `committed_at` timestamp, so the array also serves as the contract amendment ledger: day-1 commitments share `committed_at = create_media_buy.confirmed_at`; mid-flight additions carry their own timestamps. When `create_media_buy.confirmed_at` is null for a provisional buy, sellers MUST omit `committed_metrics` until commitment. The first response that sets `confirmed_at` MAY include the initial committed-metrics set, and each such entry's `committed_at` MUST equal `confirmed_at`. The `missing_metrics` field on `get_media_buy_delivery` reconciles against this list, filtering to entries where `committed_at < reporting_period.end` (a metric committed mid-flight is only audited from its commitment timestamp forward). Sellers stamp the day-1 set on the `create_media_buy` response; mid-flight additions are appended via `update_media_buy` (append-only — sellers MUST reject attempts to modify or remove existing entries with `validation_error`, suggested code: `IMMUTABLE_FIELD`). Optional in v1; absence means the seller does not provide an audit-grade contract and `missing_metrics` falls back to the product's live `available_metrics` (a known audit gap — buyers SHOULD treat absence as 'no audit-grade contract' rather than 'clean delivery'). Each entry uses an explicit `scope` discriminator: `standard` for entries from the closed `available-metric.json` enum, `vendor` for vendor-defined metrics anchored on a BrandRef. The unified shape is symmetric with `missing_metrics` and `aggregated_totals.metric_aggregates` — same atomic unit `(scope, metric_id, qualifier)` across contract, diff, and delivery, so reconciliation collapses to a row-level join on the tuple. Replaces the parallel-array design that shipped briefly in #3510.", - examples=[ - [ - { - 'scope': 'standard', - 'metric_id': 'impressions', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'spend', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'completed_views', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'vendor', - 'vendor': {'domain': 'attentionvendor.example'}, - 'metric_id': 'attention_units', - 'committed_at': '2026-04-29T10:53:00Z', - }, - { - 'scope': 'standard', - 'metric_id': 'viewable_rate', - 'qualifier': {'viewability_standard': 'mrc'}, - 'committed_at': '2026-05-30T14:22:00Z', - }, - ] - ], - min_length=1, - ), - ] = None - creative_assignments: Annotated[ - list[CreativeAssignment2] | None, - Field(description='Creative assets assigned to this package'), - ] = None - format_ids_to_provide: Annotated[ - list[FormatIdsToProvideItem] | None, - Field(description='Format IDs that creative assets will be provided for this package'), - ] = None - optimization_goals: Annotated[ - list[OptimizationGoals4] | None, - Field( - description='Optimization targets for this package. The seller optimizes delivery toward these goals in priority order. Common pattern: event goals (purchase, install) as primary targets at priority 1; metric goals (clicks, views) as secondary proxy signals at priority 2+.', - min_length=1, - ), - ] = None - start_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight start date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's start_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - end_time: Annotated[ - AwareDatetime | None, - Field( - description="Flight end date/time for this package in ISO 8601 format. When omitted, the package inherits the media buy's end_time. Sellers SHOULD always include the resolved value in responses, even when inherited." - ), - ] = None - paused: Annotated[ - bool | None, - Field( - description='Whether this package is paused by the buyer. Paused packages do not deliver impressions. Defaults to false.' - ), - ] = False - canceled: Annotated[ - bool | None, - Field( - description='Whether this package has been canceled. Canceled packages stop delivery and cannot be reactivated. Defaults to false.' - ), - ] = False - cancellation: Annotated[ - Cancellation | None, - Field(description='Cancellation metadata. Present only when canceled is true.'), - ] = None - agency_estimate_number: Annotated[ - str | None, - Field( - description="Agency estimate or authorization number for this package. Echoed from the buyer's request. When present on the package, takes precedence over the media buy-level estimate number.", - max_length=100, - ), - ] = None - creative_deadline: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp for creative upload or change deadline for this package. After this deadline, creative changes are rejected. When absent, the media buy's creative_deadline applies." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Opaque package-level correlation data echoed unchanged in responses, webhooks, and read surfaces. Buyers targeting mixed seller populations SHOULD include a per-package correlation value here, commonly context.buyer_ref, so responses from legacy sellers that do not echo product_id can still be mapped back to the requested product or line item. Sellers MUST preserve this object unchanged and MUST NOT parse it for business logic.', - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AvailableAction1(AvailableAction): - pass - - -class Result283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError18 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig20 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - media_buy_id: Annotated[str, Field(description="Seller's identifier for the media buy")] - media_buy_status: MediaBuyStatus | None = None - revision: Annotated[ - int, - Field( - description='Revision number after this update. Use this value in subsequent update_media_buy requests intended to change state for optimistic concurrency. Exact idempotency replays return the prior revision and do not increment revision.', - ge=1, - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for monetary values at this media buy level. Echoed when the update affects budget or currency. Matches the currency field in subsequent get_media_buys responses.', - pattern='^[A-Z]{3}$', - ), - ] = None - total_budget: Annotated[ - float | None, - Field( - description='Updated total budget amount across all packages, denominated in currency. Echoed when the update affects package budgets so buyers can verify the new aggregate without a round-trip to get_media_buys. Matches the total_budget field in subsequent get_media_buys responses.', - ge=0.0, - ), - ] = None - implementation_date: Annotated[ - AwareDatetime | None, - Field(description='ISO 8601 timestamp when changes take effect (null if pending approval)'), - ] = None - invoice_recipient: Annotated[ - InvoiceRecipient1 | None, - Field( - description='Updated invoice recipient, echoed from the request when provided. Confirms the seller accepted the billing override. Bank details are omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - affected_packages: Annotated[ - list[AffectedPackage] | None, - Field( - description='For package-level updates, array of full Package objects showing complete post-update state for each directly modified package. This is a state snapshot, not a sparse delta: sellers MUST NOT return package_id-only stubs. Campaign-level updates that do not modify packages may return an empty array.' - ), - ] = None - valid_actions: Annotated[ - list[MediaBuyValidAction] | None, - Field( - description='Flat-vocabulary actions the buyer can perform after this update. Saves a round-trip to get_media_buys. Deprecated in favor of `available_actions[]`, which carries `mode`, optional SLA, and optional `terms_ref`. Sellers SHOULD populate both during the 3.x deprecation window; consumers MUST prefer `available_actions[]` when both are present. Removed in 4.0.' - ), - ] = None - available_actions: Annotated[ - list[AvailableAction1] | None, - Field( - description='Structured per-buy resolution of actions available after this update. Authoritative — see `get-media-buys-response.json` for full semantics.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig21(PushNotificationConfig18): - pass - - -class Result288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError19 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig21 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error14], - Field(description='Array of errors explaining why the operation failed', min_length=1), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig22(PushNotificationConfig18): - pass - - -class Result289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError20 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig22 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error15] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class AttributionWindow8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - post_click: Annotated[ - PostClick2 | None, - Field( - description='Post-click attribution window. Conversions occurring within this duration after a click are attributed to the ad.' - ), - ] = None - post_view: Annotated[ - PostView2 | None, - Field( - description='Post-view attribution window. Conversions occurring within this duration after an ad impression (without click) are attributed to the ad.' - ), - ] = None - model: AttributionModel | None = None - - -class ByEventTypeItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - event_type: EventType - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[float, Field(description='Number of events of this type', ge=0.0)] - value: Annotated[ - float | None, Field(description='Total monetary value of events of this type', ge=0.0) - ] = None - - -class EmbeddedProvenanceItem274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent548 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent549 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction285(Jurisdiction229): - pass - - -class Disclosure276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction285] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy274 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem274] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark274] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure276 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem274] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance274 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride68(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo68 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor48(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride68 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor48 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class ByActionSourceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action_source: ActionSource - event_source_id: Annotated[ - str | None, - Field( - description='Event source that produced these conversions (for disambiguation when multiple event sources are configured)' - ), - ] = None - count: Annotated[ - float, Field(description='Number of conversions from this action source', ge=0.0) - ] - value: Annotated[ - float | None, - Field(description='Total monetary value of conversions from this action source', ge=0.0), - ] = None - - -class EmbeddedProvenanceItem275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent550 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent551 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction286(Jurisdiction229): - pass - - -class Disclosure277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction286] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance275(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy275 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem275] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark275] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure277 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem275] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance275 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride69(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo69 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor49(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride69 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue11(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor49, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class Totals(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability13 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue11] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - effective_rate: Annotated[float | None, Field(ge=0.0)] = None - - -class EmbeddedProvenanceItem276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent552 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent553 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction287(Jurisdiction229): - pass - - -class Disclosure278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction287] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance276(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy276 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem276] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark276] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure278 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem276] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance276 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride70(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo70 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor50(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride70 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor50 | None, - Field( - description="Vendor that produced these viewability values. Optional but RECOMMENDED so the row is self-describing — buyer agents reading delivery in isolation can attribute the numbers without joining back to `package.committed_metrics` or `package.performance_standards`. The vendor's `brand.json` `agents[type='measurement']` is the discovery anchor; the metric definitions live on the agent's `get_adcp_capabilities.measurement.metrics[]` block. Same shape as `vendor_metric_value.vendor` for symmetry across vendor-attested surfaces.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Impressions where viewability could be measured. Excludes environments without measurement capability (e.g., non-Intersection Observer browsers, certain app environments). Coverage denominator for `viewable_rate` AND `viewed_seconds` — both metrics are computed over the same measurable population.', - ge=0.0, - ), - ] = None - viewable_impressions: Annotated[ - float | None, - Field( - description='Impressions that met the viewability threshold defined by the measurement standard.', - ge=0.0, - ), - ] = None - viewable_rate: Annotated[ - float | None, - Field( - description='Viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.', - ge=0.0, - le=1.0, - ), - ] = None - viewed_seconds: Annotated[ - float | None, - Field( - description="Average in-view duration per measurable impression, in seconds. Reporting-side counterpart to the `viewed_seconds` optimization metric in `optimization-goal.json`. Computed over `measurable_impressions`, not total impressions — the same denominator as `viewable_rate`. The viewability `standard` governs the threshold (e.g., MRC's 50% pixels for 1s display / 2s video) that defines when an impression is in view and therefore when the clock is running. Sellers reporting against a `viewed_seconds` optimization goal MUST populate this field.", - ge=0.0, - ), - ] = None - standard: ViewabilityStandard | None = None - - -class EmbeddedProvenanceItem277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent554 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent555 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction288(Jurisdiction229): - pass - - -class Disclosure279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction288] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance277(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy277 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem277] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark277] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure279 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem277] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance277 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride71(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo71 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor51(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride71 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue12(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor51, - Field( - description='Vendor that produced this value. Matches a `vendor_metrics[].vendor` declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a `vendor_metrics[].metric_id` declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - float, - Field( - description="The reported value. Unit semantics are vendor-defined — see `unit` field below and the vendor's `brand.json` measurement-agent documentation." - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate the heterogeneity of vendor metrics (e.g., `score`, `seconds`, `persons`, `gCO2e`, `USD`, `lift_percent`). When the value is monetary, use the ISO 4217 code (e.g., `USD`, `EUR`); for non-monetary, use whatever the vendor publishes. Optional on every row — the canonical unit lives at the vendor's measurement-agent metric definition (`brand.json` `agents[type='measurement']`). When sellers populate it inline they SHOULD match the vendor's published unit; buyers MAY resolve from the vendor's measurement agent when this field is absent.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - float | None, - Field( - description='Number of impressions in this reporting period that the vendor was able to measure. Coverage denominator — buyers compute coverage rate as `measurable_impressions / impressions`. When absent, coverage is unspecified — buyers MUST NOT compute a coverage rate or assume full coverage. When the vendor measured zero impressions but is integrated, set to 0 explicitly. When the entry is omitted from `vendor_metric_values` entirely, the buyer infers no measurement happened (no integration). This pattern parallels `viewability.measurable_impressions` (`delivery-metrics.json#/properties/viewability`), which has handled vendor coverage in the IAS/DV/MRC ecosystem for over a decade — same convention: absence is unknown, not full.', - ge=0.0, - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that don't fit a single scalar — panel demographic breakouts, co-view audience composition, incremental reach + frequency + lift decompositions. Free-form; the keys and value semantics are defined by the vendor (see the vendor's `brand.json` measurement-agent docs). Buyers MUST treat this object as opaque without consulting the vendor's documentation. Vendors place any fields beyond the standard envelope (e.g., confidence intervals, panel sizes) inside this object rather than at the top level." - ), - ] = None - - -class ByPackageItem(AdCPBaseModel): - impressions: Annotated[float | None, Field(description='Impressions delivered', ge=0.0)] = None - spend: Annotated[float, Field(description='Amount spent', ge=0.0)] - clicks: Annotated[float | None, Field(description='Total clicks', ge=0.0)] = None - ctr: Annotated[ - float | None, Field(description='Click-through rate (clicks/impressions)', ge=0.0, le=1.0) - ] = None - views: Annotated[ - float | None, - Field( - description="Content engagements counted toward the billable view threshold. For video this is a platform-defined view event (e.g., 30 seconds or video midpoint); for audio/podcast it is a stream start; for other formats it follows the pricing model's view definition. When the package uses CPV pricing, spend = views × rate.", - ge=0.0, - ), - ] = None - completed_views: Annotated[ - float | None, - Field( - description='Video/audio completions. When the package has a completed_views optimization goal with view_duration_seconds, completions are counted at that threshold rather than 100% completion.', - ge=0.0, - ), - ] = None - completion_rate: Annotated[ - float | None, - Field(description='Completion rate (completed_views/impressions)', ge=0.0, le=1.0), - ] = None - conversions: Annotated[ - float | None, - Field( - description='Total conversions attributed to this delivery. When by_event_type is present, this equals the sum of all by_event_type[].count entries.', - ge=0.0, - ), - ] = None - conversion_value: Annotated[ - float | None, - Field( - description='Total monetary value of attributed conversions (in the reporting currency)', - ge=0.0, - ), - ] = None - roas: Annotated[ - float | None, Field(description='Return on ad spend (conversion_value / spend)', ge=0.0) - ] = None - cost_per_acquisition: Annotated[ - float | None, Field(description='Cost per conversion (spend / conversions)', ge=0.0) - ] = None - new_to_brand_rate: Annotated[ - float | None, - Field( - description='Fraction of `conversions` (transactions) from first-time brand buyers, 0 = none, 1 = all. For retail-media unit-volume tracking of first-time buyers, see `new_to_brand_units` (count, not rate).', - ge=0.0, - le=1.0, - ), - ] = None - leads: Annotated[ - float | None, - Field( - description="Leads generated (convenience alias for by_event_type where event_type='lead')", - ge=0.0, - ), - ] = None - incremental_sales_lift: Annotated[ - float | None, - Field( - description="Incremental sales lift attributed to the campaign — sales above the control/holdout baseline. Reported as a fraction (0.15 = 15% lift) or as an absolute value depending on seller convention. The seller's `attribution_methodology` qualifier (typically `deterministic_purchase` or `modeled`) and `attribution_window` qualifier on the matching `committed_metrics` entry disambiguate the methodology and window.", - ge=0.0, - ), - ] = None - brand_lift: Annotated[ - float | None, - Field( - description="Brand lift — measured change in a brand metric (awareness, consideration, favorability, purchase intent, or ad recall) attributed to the campaign. Typically panel-based or survey-based. Reported as a fraction (0.05 = 5% lift). **Multidimensional in production** — Kantar, Upwave, Cint, DV all report each dimension separately with its own sample size and confidence interval. The dimension flows through `qualifier.lift_dimension` on `committed_metrics` / `metric_aggregates` (`awareness` | `consideration` | `favorability` | `purchase_intent` | `ad_recall`); rows under different dimensions are different surveyed outcomes and must not be combined. Use `attribution_methodology: 'panel_based'` qualifier when the underlying methodology is a panel.", - ge=0.0, - ), - ] = None - foot_traffic: Annotated[ - float | None, - Field( - description="Store visits attributed to ad exposure. Count of incremental visits over baseline. Typically uses location-data panel methodology (`attribution_methodology: 'panel_based'`) or deterministic loyalty-card match (`attribution_methodology: 'deterministic_purchase'`).", - ge=0.0, - ), - ] = None - conversion_lift: Annotated[ - float | None, - Field( - description='Incremental conversions attributed to the campaign — conversions above the control/holdout baseline. Reported as a fraction (0.10 = 10% lift) or as an absolute count depending on seller convention. Distinct from `conversions` (raw count of attributed conversions); conversion_lift requires a control group and an incrementality methodology.', - ge=0.0, - ), - ] = None - brand_search_lift: Annotated[ - float | None, - Field( - description='Lift in brand search query volume attributed to the campaign — measured via search-data partnerships (Google, Microsoft) or survey methodology. Reported as a fraction (0.20 = 20% lift in branded search).', - ge=0.0, - ), - ] = None - plays: Annotated[ - float | None, - Field( - description="Number of times the ad creative was displayed on a DOOH screen or played in a loop. Raw play count before any impression multiplier is applied. Mirrors `forecastable-metric.json`'s `plays` token for forecast↔delivery reconciliation. Distinct from `dooh_metrics.loop_plays` (per-screen rotation count) and from `impressions` (multiplied audience figure). Used for DOOH and broadcast inventory where buyers reconcile against forecast `plays`.", - ge=0.0, - ), - ] = None - by_event_type: Annotated[ - list[ByEventTypeItem] | None, - Field( - description='Conversion metrics broken down by event type. Spend-derived metrics (ROAS, CPA) are only available at the package/totals level since spend cannot be attributed to individual event types.' - ), - ] = None - grps: Annotated[ - float | None, Field(description='Gross Rating Points delivered (for CPP)', ge=0.0) - ] = None - reach: Annotated[ - float | None, - Field( - description='Unique reach in the units specified by reach_unit. When reach_unit is omitted, units are unspecified — do not compare reach values across packages or media buys without a common reach_unit. The measurement window for this value is declared in `reach_window`; when `reach_window` is omitted, the window is unspecified and buyers MUST NOT sum reach across reports (the value MAY be a daily snapshot, a cumulative total, or something else).', - ge=0.0, - ), - ] = None - reach_unit: Annotated[ - ReachUnit | None, - Field( - description='Unit of measurement for the reach field. Aligns with the reach_unit declared on optimization goals and delivery forecasts. Required when reach is present to enable cross-platform comparison.' - ), - ] = None - reach_window: Annotated[ - ReachWindow5 | None, - Field( - description='Measurement window for the reported `reach` and `frequency` values in this row. Declares whether the values are a per-period snapshot, a trailing rolling window, or cumulative-to-date — without this declaration, buyers summing `reach` across rows (e.g., daily delivery reports) can silently double-count audiences. Sellers SHOULD populate this whenever `reach` is present.' - ), - ] = None - frequency: Annotated[ - float | None, - Field( - description="Average frequency per reach unit, measured over the window declared in `reach_window`. When `reach_unit` is 'households', this is average exposures per household; when 'accounts', per logged-in account; etc. When `reach_window` is omitted, the window is unspecified — buyers MUST NOT compare or average frequency values across rows.", - ge=0.0, - ), - ] = None - quartile_data: Annotated[ - QuartileData | None, Field(description='Audio/video quartile completion data') - ] = None - dooh_metrics: Annotated[ - DoohMetrics4 | None, - Field(description='DOOH-specific metrics (only included for DOOH campaigns)'), - ] = None - viewability: Annotated[ - Viewability14 | None, - Field( - description='Viewability metrics. Viewable rate should be calculated as viewable_impressions / measurable_impressions (not total impressions), since some environments cannot measure viewability. Includes `viewed_seconds` — average in-view duration — since duration is governed by the same viewability threshold (`standard`) and shares the same `measurable_impressions` denominator. Sellers SHOULD include `standard` whenever measured viewability values are reported because MRC and GroupM rows are not interchangeable.' - ), - ] = None - engagements: Annotated[ - float | None, - Field( - description="Total engagements — direct interactions with the ad beyond viewing. Includes social reactions/comments/shares, story/unit opens, interactive overlay taps on CTV, companion banner interactions on audio. Platform-specific; corresponds to the 'engagements' optimization metric. Maps to DBCFM KPI_INTERACTIONS (Interaktionen) in the Reporting/Performance block.", - ge=0.0, - ), - ] = None - follows: Annotated[ - float | None, - Field( - description='New followers, page likes, artist/podcast/channel follows, or free channel/feed subscribes attributed to this delivery. Paid subscriptions are conversion events with `event_type: subscribe`, not `follows`.', - ge=0.0, - ), - ] = None - saves: Annotated[ - float | None, - Field( - description='Saves, bookmarks, playlist adds, pins attributed to this delivery.', ge=0.0 - ), - ] = None - profile_visits: Annotated[ - float | None, - Field( - description="Visits to the brand's in-platform page (profile, artist page, channel, or storefront) attributed to this delivery. Does not include external website clicks.", - ge=0.0, - ), - ] = None - engagement_rate: Annotated[ - float | None, - Field( - description='Platform-specific engagement rate (0.0 to 1.0). Typically engagements/impressions, but definition varies by platform.', - ge=0.0, - le=1.0, - ), - ] = None - cost_per_click: Annotated[ - float | None, Field(description='Cost per click (spend / clicks)', ge=0.0) - ] = None - cost_per_completed_view: Annotated[ - float | None, - Field( - description="Cost per completed view (spend / completed_views). Primary CPCV pricing scalar for video/audio inventory; the package's `pricing_model` is `cpcv` when this field is the billing basis.", - ge=0.0, - ), - ] = None - cpm: Annotated[ - float | None, - Field( - description="Cost per thousand impressions, computed as (spend / impressions) × 1000. Universal pricing scalar across CTV, display, mobile/web video, native, audio, and DOOH inventory; the package's `pricing_model` is `cpm` when this field is the billing basis. Field name aligns with the canonical `cpm` token in `enums/pricing-model.json` and `pricing-options/cpm-option.json` so buyers cross-walk pricing model → reported scalar without a translation table.", - ge=0.0, - ), - ] = None - downloads: Annotated[ - float | None, - Field( - description="Audio/podcast downloads (IAB Podcast Measurement Technical Guidelines 2.x methodology). Distinct from `views` — for podcast inventory this is the count of podcast episode downloads; for streaming audio it is the count of stream starts that meet the platform's download threshold. Prefer this over `views` for audio inventory.", - ge=0.0, - ), - ] = None - units_sold: Annotated[ - float | None, - Field( - description='Items sold attributed to this delivery. Retail-media scalar distinct from `conversions` — a single conversion (transaction) may carry multiple `units_sold`. Used by retail media platforms where the buyer optimizes against unit movement, not transaction count. Attribution lookback windows are platform-specific (commonly 7/14/30 days, view-through and click-through variants); sellers SHOULD declare the window via `reporting_capabilities.measurement_windows` or `measurement_terms` rather than encoding it in this scalar.', - ge=0.0, - ), - ] = None - new_to_brand_units: Annotated[ - float | None, - Field( - description='Units sold to first-time brand buyers (count, not rate). Retail-media scalar — the unit-volume parallel to the conversion-fraction `new_to_brand_rate`. Used by retail media platforms where new-customer acquisition unit volume is a primary KPI. Same attribution-window note as `units_sold` applies.', - ge=0.0, - ), - ] = None - by_action_source: Annotated[ - list[ByActionSourceItem] | None, - Field( - description='Conversion metrics broken down by action source (website, app, in_store, etc.). Useful for omnichannel sellers where conversions occur across digital and physical channels.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue12] | None, - Field( - description="Reported values for vendor-defined metrics that the product's `reporting_capabilities.vendor_metrics` declared. Each entry carries the vendor (BrandRef), the metric identifier within the vendor's vocabulary, the value, optional unit, and `measurable_impressions` as the coverage denominator — vendor measurement is rarely 100% of delivered impressions, since vendors only score impressions where their SDK fires or their panel matches. When a declared vendor metric is omitted from this array, buyers infer no measurement happened (no integration). One row per `(vendor.domain, vendor.brand_id, metric_id)` per reporting period — sellers MUST de-duplicate before emission and MUST NOT emit the same vendor metric twice; buyers MAY treat duplicate rows as a seller-side conformance bug. The structured `vendor_metric_values` array is the recommended path for vendor metrics; `additionalProperties: true` on this parent object is preserved so existing free-form vendor emissions remain conformant during migration." - ), - ] = None - package_id: str | None = None - pacing_index: Annotated[float | None, Field(ge=0.0)] = None - pricing_model: PricingModel | None = None - rate: Annotated[float | None, Field(ge=0.0)] = None - currency: Annotated[str | None, Field(pattern='^[A-Z]{3}$')] = None - delivery_status: DeliveryStatus | None = None - paused: bool | None = None - is_final: bool | None = None - finalized_at: AwareDatetime | None = None - measurement_window: Annotated[str | None, Field(max_length=50)] = None - supersedes_window: Annotated[str | None, Field(max_length=50)] = None - - -class MediaBuyDelivery(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy_id: Annotated[str, Field(description="Seller's media buy identifier.")] - status: Annotated[ - Status141, - Field( - description="Current media buy lifecycle or reporting status. This is distinct from the webhook envelope's top-level task status." - ), - ] - expected_availability: Annotated[ - AwareDatetime | None, - Field( - description='When delayed data is expected to be available. Present when status is reporting_delayed.' - ), - ] = None - is_adjusted: Annotated[ - bool | None, - Field( - description='Indicates this row contains updated data for a previously reported period.' - ), - ] = None - is_final: Annotated[ - bool | None, - Field(description="Whether this row's delivery data is final for the reporting period."), - ] = None - finalized_at: Annotated[ - AwareDatetime | None, - Field( - description='Timestamp when this row became final. Present only when is_final is true.' - ), - ] = None - pricing_model: PricingModel | None = None - totals: Totals - by_package: Annotated[list[ByPackageItem], Field(description='Metrics broken down by package.')] - - -class Result293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - notification_type: Annotated[ - NotificationType1, - Field( - description='Type of delivery-report notification: scheduled = regular periodic update, final = campaign completed, delayed = data not yet available, adjusted = corrected data for the same window, window_update = a wider measurement window supersedes a prior window.' - ), - ] - partial_data: Annotated[ - bool | None, - Field( - description='Indicates if any media buys in this webhook have missing or delayed data.' - ), - ] = None - unavailable_count: Annotated[ - int | None, - Field( - description='Number of media buys with reporting_delayed or failed status when partial_data is true.', - ge=0, - ), - ] = None - sequence_number: Annotated[ - int | None, - Field( - description='Sequential notification number for this reporting webhook stream.', ge=1 - ), - ] = None - next_expected_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp for the next expected notification. Omitted on final notifications.' - ), - ] = None - reporting_period: Annotated[ - ReportingPeriod, Field(description='UTC date range covered by the delivery report.') - ] - currency: Annotated[str, Field(description='ISO 4217 currency code.', pattern='^[A-Z]{3}$')] - attribution_window: Annotated[ - AttributionWindow8 | None, - Field( - description='Attribution methodology and lookback windows used for conversion metrics in this report.', - title='Attribution Window', - ), - ] = None - media_buy_deliveries: Annotated[ - list[MediaBuyDelivery], - Field( - description='Delivery rows for one or more media buys included in this notification.' - ), - ] - errors: Annotated[ - list[Error17] | None, Field(description='Task-specific delivery errors or warnings.') - ] = None - sandbox: bool | None = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig23(PushNotificationConfig18): - pass - - -class EmbeddedProvenanceItem278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent556 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent557 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction289(Jurisdiction229): - pass - - -class Disclosure280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction289] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy278 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem278] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark278] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure280 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem278] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance278 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent558 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent559 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction290(Jurisdiction229): - pass - - -class Disclosure281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction290] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy279 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem279] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark279] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure281 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem279] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance279 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent560 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent561 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction291(Jurisdiction229): - pass - - -class Disclosure282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction291] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy280 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem280] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark280] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure282 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem280] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance280 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent562 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent563 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction292(Jurisdiction229): - pass - - -class Disclosure283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction292] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy281 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem281] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark281] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure283 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem281] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance281 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent564 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent565 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction293(Jurisdiction229): - pass - - -class Disclosure284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction293] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy282 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem282] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark282] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure284 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem282] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets150(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance282 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent566 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent567 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction294(Jurisdiction229): - pass - - -class Disclosure285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction294] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance283(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy283 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem283] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark283] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure285 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem283] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets151(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance283 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent568 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent569 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction295(Jurisdiction229): - pass - - -class Disclosure286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction295] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance284(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy284 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem284] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark284] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure286 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem284] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets152(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance284 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent570 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent571 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction296(Jurisdiction229): - pass - - -class Disclosure287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction296] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance285(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy285 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem285] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark285] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure287 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem285] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets153(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance285 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent572 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent573 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction297(Jurisdiction229): - pass - - -class Disclosure288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction297] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance286(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy286 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem286] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark286] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure288 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem286] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets154(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance286 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent574 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent575 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction298(Jurisdiction229): - pass - - -class Disclosure289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction298] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance287(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy287 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem287] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark287] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure289 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem287] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets155(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance287 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Security(AdCPBaseModel): - method: WebhookSecurityMethod - hmac_header: Annotated[ - str | None, Field(description="Header name for HMAC signature (e.g., 'X-Signature')") - ] = None - api_key_header: Annotated[ - str | None, Field(description="Header name for API key (e.g., 'X-API-Key')") - ] = None - - -class EmbeddedProvenanceItem288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent576 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent577 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction299(Jurisdiction229): - pass - - -class Disclosure290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction299] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance288(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy288 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem288] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark288] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure290 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem288] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets156(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance288 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent578 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent579 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction300(Jurisdiction229): - pass - - -class Disclosure291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction300] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance289(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy289 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem289] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark289] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure291 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem289] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets157(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance289 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent580 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent581 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction301(Jurisdiction229): - pass - - -class Disclosure292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction301] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance290(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy290 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem290] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark290] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure292 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem290] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets158(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance290 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent582 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent583 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction302(Jurisdiction229): - pass - - -class Disclosure293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction302] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance291(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy291 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem291] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark291] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure293 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem291] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets159(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance291 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets160(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['markdown'], - Field( - description='Discriminator identifying this as a markdown asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'markdown' - content: Annotated[ - str, - Field( - description='Markdown content following CommonMark spec with optional GitHub Flavored Markdown extensions' - ), - ] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - markdown_flavor: MarkdownFlavor | None = MarkdownFlavor.commonmark - allow_raw_html: Annotated[ - bool | None, - Field( - description='Whether raw HTML blocks are allowed in the markdown. False recommended for security.' - ), - ] = False - - -class RequiredDisclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - text: Annotated[str, Field(description='The disclosure text that must appear in the creative')] - position: DisclosurePosition | None = None - jurisdictions: Annotated[ - list[Jurisdiction303] | None, - Field( - description="Jurisdictions where this disclosure is required. ISO 3166-1 alpha-2 country codes or ISO 3166-2 subdivision codes (e.g., 'US', 'GB', 'US-NJ', 'CA-QC'). If omitted, the disclosure applies to all jurisdictions in the campaign.", - min_length=1, - ), - ] = None - regulation: Annotated[ - str | None, - Field( - description="The regulation or legal authority requiring this disclosure (e.g., 'SEC Rule 156', 'FCA COBS 4.5', 'FDA 21 CFR 202')" - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description='Minimum display duration in milliseconds. For video/audio disclosures, how long the disclosure must be visible or audible. For static formats, how long the disclosure must remain on screen before any auto-advance.', - ge=1, - ), - ] = None - language: Annotated[ - str | None, - Field( - description="Language of the disclosure text as a BCP 47 language tag (e.g., 'en', 'fr-CA', 'es'). When omitted, the disclosure is assumed to match the creative's language." - ), - ] = None - persistence: DisclosurePersistence | None = None - - -class Compliance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets161(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets162(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping10] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent584 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent585 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction304(Jurisdiction229): - pass - - -class Disclosure294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction304] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance292(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy292 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem292] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark292] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure294 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem292] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets163(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance292 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent586 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent587 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction305(Jurisdiction229): - pass - - -class Disclosure295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction305] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance293(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy293 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem293] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark293] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure295 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem293] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance293 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent588 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent589 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction306(Jurisdiction229): - pass - - -class Disclosure296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction306] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance294(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy294 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem294] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark294] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure296 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem294] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance294 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent590 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent591 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction307(Jurisdiction229): - pass - - -class Disclosure297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction307] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance295(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy295 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem295] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark295] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure297 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem295] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance295 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent592 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent593 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction308(Jurisdiction229): - pass - - -class Disclosure298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction308] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance296(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy296 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem296] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark296] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure298 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem296] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets164(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media | Media17, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance296 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent594 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent595 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction309(Jurisdiction229): - pass - - -class Disclosure299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction309] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance297(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy297 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem297] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark297] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure299 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem297] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets165(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance297 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent596 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent597 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction310(Jurisdiction229): - pass - - -class Disclosure300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction310] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy298 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem298] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark298] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure300 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem298] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets166(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance298 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent598 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent599 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction311(Jurisdiction229): - pass - - -class Disclosure301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction311] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance299(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy299 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem299] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark299] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure301 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem299] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets167(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance299 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent600 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent601 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction312(Jurisdiction229): - pass - - -class Disclosure302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction312] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance300(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy300 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem300] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark300] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure302 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem300] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1682(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance300 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent602 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent603 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction313(Jurisdiction229): - pass - - -class Disclosure303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction313] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance301(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy301 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem301] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark301] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure303 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem301] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1683(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance301 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent604 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent605 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction314(Jurisdiction229): - pass - - -class Disclosure304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction314] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance302(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy302 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem302] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark302] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure304 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem302] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1684(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance302 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent606 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent607 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction315(Jurisdiction229): - pass - - -class Disclosure305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction315] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance303(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy303 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem303] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark303] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure305 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem303] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1685(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance303 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent608 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent609 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction316(Jurisdiction229): - pass - - -class Disclosure306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction316] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance304(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy304 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem304] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark304] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure306 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem304] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1686(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance304 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent610 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent611 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction317(Jurisdiction229): - pass - - -class Disclosure307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction317] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance305(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy305 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem305] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark305] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure307 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem305] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1687(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance305 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent612 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent613 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction318(Jurisdiction229): - pass - - -class Disclosure308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction318] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance306(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy306 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem306] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark306] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure308 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem306] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1688(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance306 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent614 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent615 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction319(Jurisdiction229): - pass - - -class Disclosure309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction319] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance307(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy307 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem307] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark307] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure309 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem307] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1689(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance307 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent616 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent617 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction320(Jurisdiction229): - pass - - -class Disclosure310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction320] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance308(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy308 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem308] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark308] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure310 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem308] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16810(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance308 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent618 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent619 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction321(Jurisdiction229): - pass - - -class Disclosure311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction321] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance309(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy309 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem309] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark309] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure311 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem309] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16811(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance309 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent620 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent621 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction322(Jurisdiction229): - pass - - -class Disclosure312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction322] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy310 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem310] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark310] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure312 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem310] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16812(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance310 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent622 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent623 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction323(Jurisdiction229): - pass - - -class Disclosure313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction323] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy311 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem311] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark311] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure313 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem311] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16813(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance311 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent624 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent625 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction324(Jurisdiction229): - pass - - -class Disclosure314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction324] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy312 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem312] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark312] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure314 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem312] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16814(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance312 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent626 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent627 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction325(Jurisdiction229): - pass - - -class Disclosure315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction325] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy313 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem313] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark313] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure315 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem313] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16815(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance313 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure10(RequiredDisclosure): - pass - - -class Compliance10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure10] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets16817(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset10] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance10 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets16818(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping11] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent628 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent629 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction327(Jurisdiction229): - pass - - -class Disclosure316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction327] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy314 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem314] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark314] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure316 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem314] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16819(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization9 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance314 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent630 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent631 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction328(Jurisdiction229): - pass - - -class Disclosure317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction328] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy315 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem315] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark315] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure317 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem315] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance315 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent632 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent633 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction329(Jurisdiction229): - pass - - -class Disclosure318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction329] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance316(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy316 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem316] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark316] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure318 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem316] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance316 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent634 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent635 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction330(Jurisdiction229): - pass - - -class Disclosure319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction330] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance317(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy317 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem317] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark317] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure319 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem317] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance317 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent636 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent637 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction331(Jurisdiction229): - pass - - -class Disclosure320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction331] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance318(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy318 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem318] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark318] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure320 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem318] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16820(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media18 | Media19, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl9 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance318 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent638 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent639 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction332(Jurisdiction229): - pass - - -class Disclosure321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction332] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy319 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem319] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark319] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure321 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem319] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16821(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance319 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent640 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent641 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction333(Jurisdiction229): - pass - - -class Disclosure322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction333] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy320 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem320] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark320] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure322 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem320] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16822(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance320 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent642 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent643 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction334(Jurisdiction229): - pass - - -class Disclosure323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction334] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy321 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem321] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark321] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure323 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem321] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets16823(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance321 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets1681( - RootModel[ - Assets1682 - | Assets1683 - | Assets1684 - | Assets1685 - | Assets1686 - | Assets1687 - | Assets1688 - | Assets1689 - | Assets16810 - | Assets16811 - | Assets16812 - | Assets16813 - | Assets16814 - | Assets16815 - | Assets160 - | Assets16817 - | Assets16818 - | Assets16819 - | Assets16820 - | Assets16821 - | Assets16822 - | Assets16823 - ] -): - root: Annotated[ - Assets1682 - | Assets1683 - | Assets1684 - | Assets1685 - | Assets1686 - | Assets1687 - | Assets1688 - | Assets1689 - | Assets16810 - | Assets16811 - | Assets16812 - | Assets16813 - | Assets16814 - | Assets16815 - | Assets160 - | Assets16817 - | Assets16818 - | Assets16819 - | Assets16820 - | Assets16821 - | Assets16822 - | Assets16823, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets168(RootModel[list[Assets1681]]): - root: Annotated[list[Assets1681], Field(min_length=1)] - - -class EmbeddedProvenanceItem322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent644 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent645 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction335(Jurisdiction229): - pass - - -class Disclosure324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction335] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy322 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem322] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark322] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure324 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem322] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance322 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride72(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo72 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand22(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride72 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - rights_id: Annotated[ - str, Field(description='Rights grant identifier from the acquire_rights response') - ] - rights_agent: Annotated[RightsAgent, Field(description='The agent that granted these rights')] - valid_from: Annotated[ - AwareDatetime | None, Field(description='Start of the rights validity period') - ] = None - valid_until: Annotated[ - AwareDatetime | None, - Field( - description='End of the rights validity period. Creative should not be served after this time.' - ), - ] = None - uses: Annotated[ - list[RightUse], Field(description='Rights uses covered by this constraint', min_length=1) - ] - countries: Annotated[ - list[Country56] | None, - Field( - description='Countries where this creative may be served under these rights (ISO 3166-1 alpha-2). If omitted, no country restriction. When both countries and excluded_countries are present, the effective set is countries minus excluded_countries.' - ), - ] = None - excluded_countries: Annotated[ - list[ExcludedCountry] | None, - Field( - description='Countries excluded from rights availability (ISO 3166-1 alpha-2). Use when the grant is worldwide except specific markets.' - ), - ] = None - impression_cap: Annotated[ - int | None, - Field( - description='Maximum total impressions allowed for the full validity period (valid_from to valid_until). This is the absolute cap across all creatives using this rights grant, not a per-creative or per-period limit.', - ge=1, - ), - ] = None - right_type: RightType | None = None - approval_status: Annotated[ - ApprovalStatus | None, - Field( - description='Approval status from the rights holder at manifest creation time (snapshot, not a live value)' - ), - ] = None - verification_url: Annotated[ - AnyUrl | None, - Field( - description='URL where downstream supply chain participants can verify this rights grant is active. Returns HTTP 200 with the current grant status, or 404 if revoked. Enables SSPs and verification vendors to confirm rights before serving.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class IndustryIdentifier(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: CreativeIdentifierType - value: Annotated[ - str, - Field( - description="The identifier value (e.g., 'ABCD1234000H' for Ad-ID). Preserve the value exactly as the traffic or clearance system expects it.", - max_length=64, - ), - ] - - -class EmbeddedProvenanceItem323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent646 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent647 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction336(Jurisdiction229): - pass - - -class Disclosure325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction336] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy323 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem323] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark323] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure325 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem323] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets - | Assets147 - | Assets148 - | Assets149 - | Assets150 - | Assets151 - | Assets152 - | Assets153 - | Assets154 - | Assets155 - | Assets156 - | Assets157 - | Assets158 - | Assets159 - | Assets160 - | Assets161 - | Assets162 - | Assets163 - | Assets164 - | Assets165 - | Assets166 - | Assets167 - | Assets168, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand22 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right7] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance323 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent648 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent649 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction337(Jurisdiction229): - pass - - -class Disclosure326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction337] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance324(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy324 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem324] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark324] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure326 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem324] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets169(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance324 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent650 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent651 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction338(Jurisdiction229): - pass - - -class Disclosure327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction338] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance325(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy325 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem325] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark325] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure327 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem325] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets170(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance325 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent652 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent653 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction339(Jurisdiction229): - pass - - -class Disclosure328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction339] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance326(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy326 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem326] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark326] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure328 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem326] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets171(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance326 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent654 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent655 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction340(Jurisdiction229): - pass - - -class Disclosure329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction340] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance327(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy327 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem327] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark327] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure329 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem327] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets172(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance327 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent656 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent657 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction341(Jurisdiction229): - pass - - -class Disclosure330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction341] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance328(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy328 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem328] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark328] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure330 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem328] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets173(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance328 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent658 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent659 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction342(Jurisdiction229): - pass - - -class Disclosure331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction342] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance329(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy329 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem329] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark329] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure331 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem329] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets174(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance329 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent660 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent661 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction343(Jurisdiction229): - pass - - -class Disclosure332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction343] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance330(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy330 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem330] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark330] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure332 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem330] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets175(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance330 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent662 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent663 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction344(Jurisdiction229): - pass - - -class Disclosure333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction344] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance331(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy331 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem331] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark331] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure333 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem331] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets176(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance331 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent664 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent665 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction345(Jurisdiction229): - pass - - -class Disclosure334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction345] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance332(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy332 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem332] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark332] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure334 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem332] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets177(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance332 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent666 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent667 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction346(Jurisdiction229): - pass - - -class Disclosure335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction346] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance333(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy333 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem333] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark333] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure335 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem333] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets178(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance333 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent668 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent669 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction347(Jurisdiction229): - pass - - -class Disclosure336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction347] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance334(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy334 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem334] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark334] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure336 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem334] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets179(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance334 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent670 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent671 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction348(Jurisdiction229): - pass - - -class Disclosure337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction348] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance335(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy335 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem335] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark335] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure337 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem335] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets180(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance335 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent672 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent673 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction349(Jurisdiction229): - pass - - -class Disclosure338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction349] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance336(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy336 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem336] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark336] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure338 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem336] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets181(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance336 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent674 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent675 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction350(Jurisdiction229): - pass - - -class Disclosure339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction350] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance337(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy337 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem337] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark337] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure339 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem337] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets182(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance337 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets183(Assets160): - pass - - -class RequiredDisclosure11(RequiredDisclosure): - pass - - -class Compliance11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure11] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets184(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset11] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance11 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets185(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping12] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent676 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent677 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction352(Jurisdiction229): - pass - - -class Disclosure340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction352] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance338(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy338 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem338] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark338] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure340 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem338] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets186(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization10 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance338 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent678 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent679 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction353(Jurisdiction229): - pass - - -class Disclosure341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction353] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance339(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy339 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem339] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark339] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure341 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem339] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance339 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent680 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent681 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction354(Jurisdiction229): - pass - - -class Disclosure342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction354] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance340(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy340 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem340] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark340] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure342 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem340] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media21(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance340 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent682 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent683 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction355(Jurisdiction229): - pass - - -class Disclosure343(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction355] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance341(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy341 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem341] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark341] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure343 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem341] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl10(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance341 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent684 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent685 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction356(Jurisdiction229): - pass - - -class Disclosure344(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction356] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance342(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy342 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem342] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark342] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure344 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem342] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets187(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media20 | Media21, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl10 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance342 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem343(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent686 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark343(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent687 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction357(Jurisdiction229): - pass - - -class Disclosure345(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction357] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance343(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy343 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem343] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark343] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure345 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem343] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets188(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance343 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem344(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent688 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark344(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent689 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction358(Jurisdiction229): - pass - - -class Disclosure346(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction358] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance344(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy344 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem344] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark344] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure346 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem344] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets189(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance344 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem345(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent690 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark345(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent691 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction359(Jurisdiction229): - pass - - -class Disclosure347(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction359] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance345(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy345 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem345] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark345] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure347 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem345] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets190(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance345 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem346(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent692 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark346(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent693 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction360(Jurisdiction229): - pass - - -class Disclosure348(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction360] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance346(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy346 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem346] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark346] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure348 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem346] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1912(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance346 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem347(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent694 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark347(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent695 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction361(Jurisdiction229): - pass - - -class Disclosure349(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction361] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance347(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy347 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem347] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark347] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure349 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem347] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1913(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance347 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem348(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent696 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark348(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent697 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction362(Jurisdiction229): - pass - - -class Disclosure350(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction362] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance348(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy348 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem348] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark348] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure350 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem348] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1914(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance348 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem349(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent698 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark349(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent699 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction363(Jurisdiction229): - pass - - -class Disclosure351(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction363] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance349(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy349 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem349] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark349] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure351 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem349] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1915(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance349 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem350(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent700 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark350(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent701 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction364(Jurisdiction229): - pass - - -class Disclosure352(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction364] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance350(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy350 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem350] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark350] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure352 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem350] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1916(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance350 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem351(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent702 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark351(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent703 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction365(Jurisdiction229): - pass - - -class Disclosure353(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction365] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance351(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy351 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem351] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark351] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure353 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem351] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1917(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance351 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem352(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent704 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark352(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent705 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction366(Jurisdiction229): - pass - - -class Disclosure354(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction366] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance352(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy352 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem352] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark352] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure354 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem352] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1918(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance352 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem353(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent706 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark353(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent707 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction367(Jurisdiction229): - pass - - -class Disclosure355(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction367] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance353(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy353 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem353] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark353] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure355 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem353] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets1919(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance353 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem354(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent708 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark354(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent709 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction368(Jurisdiction229): - pass - - -class Disclosure356(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction368] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance354(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy354 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem354] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark354] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure356 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem354] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19110(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance354 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem355(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent710 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark355(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent711 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction369(Jurisdiction229): - pass - - -class Disclosure357(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction369] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance355(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy355 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem355] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark355] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure357 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem355] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19111(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance355 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem356(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent712 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark356(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent713 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction370(Jurisdiction229): - pass - - -class Disclosure358(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction370] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance356(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy356 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem356] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark356] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure358 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem356] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19112(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance356 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem357(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent714 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark357(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent715 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction371(Jurisdiction229): - pass - - -class Disclosure359(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction371] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance357(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy357 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem357] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark357] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure359 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem357] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19113(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance357 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem358(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent716 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark358(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent717 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction372(Jurisdiction229): - pass - - -class Disclosure360(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction372] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance358(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy358 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem358] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark358] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure360 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem358] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19114(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance358 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem359(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent718 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark359(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent719 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction373(Jurisdiction229): - pass - - -class Disclosure361(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction373] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance359(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy359 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem359] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark359] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure361 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem359] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19115(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance359 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure12(RequiredDisclosure): - pass - - -class Compliance12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure12] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets19117(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset12] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance12 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets19118(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping13] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem360(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent720 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark360(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent721 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction375(Jurisdiction229): - pass - - -class Disclosure362(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction375] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance360(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy360 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem360] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark360] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure362 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem360] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19119(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization11 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance360 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem361(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent722 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark361(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent723 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction376(Jurisdiction229): - pass - - -class Disclosure363(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction376] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance361(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy361 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem361] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark361] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure363 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem361] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media22(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance361 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem362(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent724 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark362(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent725 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction377(Jurisdiction229): - pass - - -class Disclosure364(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction377] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance362(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy362 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem362] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark362] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure364 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem362] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance362 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem363(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent726 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark363(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent727 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction378(Jurisdiction229): - pass - - -class Disclosure365(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction378] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance363(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy363 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem363] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark363] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure365 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem363] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl11(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance363 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem364(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent728 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark364(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent729 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction379(Jurisdiction229): - pass - - -class Disclosure366(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction379] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance364(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy364 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem364] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark364] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure366 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem364] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19120(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media22 | Media23, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl11 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance364 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem365(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent730 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark365(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent731 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction380(Jurisdiction229): - pass - - -class Disclosure367(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction380] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance365(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy365 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem365] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark365] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure367 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem365] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19121(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance365 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem366(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent732 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark366(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent733 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction381(Jurisdiction229): - pass - - -class Disclosure368(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction381] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance366(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy366 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem366] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark366] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure368 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem366] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19122(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance366 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem367(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent734 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark367(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent735 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction382(Jurisdiction229): - pass - - -class Disclosure369(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction382] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance367(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy367 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem367] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark367] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure369 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem367] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets19123(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance367 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets1911( - RootModel[ - Assets1912 - | Assets1913 - | Assets1914 - | Assets1915 - | Assets1916 - | Assets1917 - | Assets1918 - | Assets1919 - | Assets19110 - | Assets19111 - | Assets19112 - | Assets19113 - | Assets19114 - | Assets19115 - | Assets160 - | Assets19117 - | Assets19118 - | Assets19119 - | Assets19120 - | Assets19121 - | Assets19122 - | Assets19123 - ] -): - root: Annotated[ - Assets1912 - | Assets1913 - | Assets1914 - | Assets1915 - | Assets1916 - | Assets1917 - | Assets1918 - | Assets1919 - | Assets19110 - | Assets19111 - | Assets19112 - | Assets19113 - | Assets19114 - | Assets19115 - | Assets160 - | Assets19117 - | Assets19118 - | Assets19119 - | Assets19120 - | Assets19121 - | Assets19122 - | Assets19123, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets191(RootModel[list[Assets1911]]): - root: Annotated[list[Assets1911], Field(min_length=1)] - - -class EmbeddedProvenanceItem368(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent736 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark368(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent737 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction383(Jurisdiction229): - pass - - -class Disclosure370(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction383] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance368(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy368 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem368] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark368] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure370 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem368] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance368 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride73(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo73 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride73 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right8(Right7): - pass - - -class EmbeddedProvenanceItem369(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent738 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark369(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent739 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction384(Jurisdiction229): - pass - - -class Disclosure371(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction384] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance369(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy369 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem369] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark369] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure371 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem369] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest6(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets169 - | Assets170 - | Assets171 - | Assets172 - | Assets173 - | Assets174 - | Assets175 - | Assets176 - | Assets177 - | Assets178 - | Assets179 - | Assets180 - | Assets181 - | Assets182 - | Assets183 - | Assets184 - | Assets185 - | Assets186 - | Assets187 - | Assets188 - | Assets189 - | Assets190 - | Assets191, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand23 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right8] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance369 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result298(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError21 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig23 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest | CreativeManifest6, - Field( - description='The generated or transformed creative manifest', title='Creative Manifest' - ), - ] - build_variant_id: Annotated[ - str | None, - Field( - description='Leaf handle for this produced creative — present when the agent supports refinement (creative.supports_refinement). Pass it as refine_from_build_variant_id to refine this build. Same namespace as BuildCreativeVariantSuccess leaves; distinct from served variant_id / preview_id. On the canonical promotion path, this value becomes the creative_id when the produced leaf is trafficked or added to the library.' - ), - ] = None - recipe_hash: Annotated[ - str | None, - Field( - description='Optional agent-computed, opaque, agent-scoped identity for the build-determining inputs that produced this creative. Stable for identical inputs as defined by the agent, and comparable only within the same agent. ETag-style semantics: the protocol defines the field and contract, not the hash algorithm or canonical input set. Identifies generative-input identity, not output equality, legal/disclosure equivalence, or the build-to-delivery join.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when generated asset URLs in the manifest expire. Set to the earliest expiration across all generated assets. Re-build the creative after this time to get fresh URLs.' - ), - ] = None - preview: Annotated[ - Preview | None, - Field( - description='Preview renders included when the request set include_preview to true and the agent supports it. Contains the same content fields as a preview_creative single response (previews, interactive_url, expires_at) minus the response_type discriminator, so clients can reuse the same preview rendering logic.' - ), - ] = None - preview_error: Annotated[ - PreviewError | None, - Field( - description="When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Distinguishes 'agent does not support inline preview' (preview and preview_error both absent) from 'preview generation failed' (preview absent, preview_error present). Omitted when preview succeeded or was not requested.", - title='Error', - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate card pricing option was applied for this build. Present when the creative agent charges for its services. Pass this in report_usage to identify which pricing option was applied.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Cost incurred for this build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time rather than build time.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card. vendor_cost is the billing source of truth.', - title='Creative Consumption', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig24(PushNotificationConfig18): - pass - - -class EmbeddedProvenanceItem370(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent740 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark370(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent741 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction385(Jurisdiction229): - pass - - -class Disclosure372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction385] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance370(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy370 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem370] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark370] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure372 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem370] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets192(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance370 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem371(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent742 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark371(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent743 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction386(Jurisdiction229): - pass - - -class Disclosure373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction386] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance371(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy371 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem371] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark371] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure373 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem371] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets193(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance371 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent744 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent745 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction387(Jurisdiction229): - pass - - -class Disclosure374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction387] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy372 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem372] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark372] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure374 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem372] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets194(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance372 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent746 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent747 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction388(Jurisdiction229): - pass - - -class Disclosure375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction388] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy373 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem373] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark373] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure375 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem373] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets195(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance373 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent748 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent749 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction389(Jurisdiction229): - pass - - -class Disclosure376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction389] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy374 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem374] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark374] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure376 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem374] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets196(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance374 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent750 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent751 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction390(Jurisdiction229): - pass - - -class Disclosure377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction390] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy375 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem375] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark375] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure377 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem375] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets197(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance375 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent752 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent753 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction391(Jurisdiction229): - pass - - -class Disclosure378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction391] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy376 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem376] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark376] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure378 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem376] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets198(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance376 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent754 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent755 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction392(Jurisdiction229): - pass - - -class Disclosure379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction392] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy377 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem377] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark377] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure379 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem377] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets199(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance377 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent756 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent757 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction393(Jurisdiction229): - pass - - -class Disclosure380(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction393] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy378 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem378] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark378] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure380 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem378] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets200(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance378 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent758 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent759 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction394(Jurisdiction229): - pass - - -class Disclosure381(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction394] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy379 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem379] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark379] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure381 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem379] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets201(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance379 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem380(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent760 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark380(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent761 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction395(Jurisdiction229): - pass - - -class Disclosure382(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction395] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance380(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy380 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem380] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark380] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure382 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem380] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets202(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance380 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem381(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent762 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark381(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent763 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction396(Jurisdiction229): - pass - - -class Disclosure383(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction396] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance381(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy381 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem381] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark381] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure383 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem381] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets203(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance381 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem382(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent764 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark382(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent765 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction397(Jurisdiction229): - pass - - -class Disclosure384(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction397] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance382(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy382 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem382] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark382] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure384 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem382] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets204(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance382 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem383(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent766 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark383(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent767 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction398(Jurisdiction229): - pass - - -class Disclosure385(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction398] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance383(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy383 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem383] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark383] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure385 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem383] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets205(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance383 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets206(Assets160): - pass - - -class RequiredDisclosure13(RequiredDisclosure): - pass - - -class Compliance13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure13] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets207(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset13] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance13 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets208(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping14] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem384(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent768 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark384(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent769 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction400(Jurisdiction229): - pass - - -class Disclosure386(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction400] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance384(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy384 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem384] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark384] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure386 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem384] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets209(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization12 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance384 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem385(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent770 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark385(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent771 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction401(Jurisdiction229): - pass - - -class Disclosure387(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction401] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance385(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy385 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem385] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark385] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure387 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem385] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media24(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance385 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem386(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent772 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark386(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent773 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction402(Jurisdiction229): - pass - - -class Disclosure388(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction402] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance386(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy386 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem386] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark386] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure388 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem386] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media25(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance386 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem387(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent774 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark387(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent775 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction403(Jurisdiction229): - pass - - -class Disclosure389(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction403] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance387(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy387 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem387] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark387] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure389 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem387] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl12(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance387 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem388(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent776 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark388(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent777 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction404(Jurisdiction229): - pass - - -class Disclosure390(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction404] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance388(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy388 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem388] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark388] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure390 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem388] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets210(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media24 | Media25, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl12 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance388 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem389(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent778 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark389(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent779 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction405(Jurisdiction229): - pass - - -class Disclosure391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction405] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance389(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy389 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem389] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark389] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure391 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem389] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets211(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance389 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem390(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent780 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark390(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent781 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction406(Jurisdiction229): - pass - - -class Disclosure392(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction406] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance390(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy390 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem390] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark390] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure392 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem390] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets212(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance390 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent782 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent783 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction407(Jurisdiction229): - pass - - -class Disclosure393(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction407] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy391 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem391] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark391] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure393 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem391] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets213(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance391 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem392(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent784 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark392(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent785 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction408(Jurisdiction229): - pass - - -class Disclosure394(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction408] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance392(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy392 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem392] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark392] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure394 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem392] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2142(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance392 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem393(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent786 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark393(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent787 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction409(Jurisdiction229): - pass - - -class Disclosure395(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction409] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance393(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy393 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem393] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark393] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure395 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem393] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2143(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance393 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem394(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent788 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark394(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent789 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction410(Jurisdiction229): - pass - - -class Disclosure396(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction410] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance394(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy394 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem394] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark394] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure396 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem394] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2144(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance394 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem395(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent790 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark395(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent791 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction411(Jurisdiction229): - pass - - -class Disclosure397(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction411] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance395(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy395 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem395] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark395] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure397 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem395] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2145(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance395 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem396(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent792 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark396(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent793 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction412(Jurisdiction229): - pass - - -class Disclosure398(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction412] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance396(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy396 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem396] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark396] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure398 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem396] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2146(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance396 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem397(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent794 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark397(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent795 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction413(Jurisdiction229): - pass - - -class Disclosure399(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction413] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance397(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy397 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem397] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark397] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure399 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem397] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2147(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance397 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem398(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent796 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark398(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent797 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction414(Jurisdiction229): - pass - - -class Disclosure400(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction414] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance398(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy398 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem398] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark398] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure400 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem398] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2148(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance398 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem399(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent798 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark399(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent799 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction415(Jurisdiction229): - pass - - -class Disclosure401(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction415] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance399(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy399 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem399] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark399] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure401 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem399] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2149(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance399 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem400(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent800 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark400(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent801 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction416(Jurisdiction229): - pass - - -class Disclosure402(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction416] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance400(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy400 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem400] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark400] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure402 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem400] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance400 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem401(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent802 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark401(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent803 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction417(Jurisdiction229): - pass - - -class Disclosure403(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction417] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance401(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy401 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem401] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark401] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure403 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem401] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance401 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem402(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent804 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark402(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent805 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction418(Jurisdiction229): - pass - - -class Disclosure404(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction418] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance402(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy402 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem402] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark402] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure404 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem402] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance402 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem403(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent806 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark403(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent807 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction419(Jurisdiction229): - pass - - -class Disclosure405(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction419] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance403(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy403 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem403] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark403] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure405 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem403] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance403 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem404(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent808 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark404(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent809 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction420(Jurisdiction229): - pass - - -class Disclosure406(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction420] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance404(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy404 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem404] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark404] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure406 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem404] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance404 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem405(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent810 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark405(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent811 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction421(Jurisdiction229): - pass - - -class Disclosure407(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction421] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance405(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy405 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem405] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark405] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure407 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem405] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance405 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure14(RequiredDisclosure): - pass - - -class Compliance14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure14] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets21417(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset14] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance14 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets21418(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping15] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem406(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent812 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark406(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent813 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction423(Jurisdiction229): - pass - - -class Disclosure408(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction423] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance406(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy406 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem406] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark406] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure408 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem406] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization13 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance406 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem407(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent814 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark407(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent815 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction424(Jurisdiction229): - pass - - -class Disclosure409(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction424] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance407(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy407 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem407] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark407] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure409 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem407] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media26(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance407 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem408(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent816 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark408(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent817 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction425(Jurisdiction229): - pass - - -class Disclosure410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction425] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance408(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy408 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem408] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark408] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure410 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem408] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance408 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem409(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent818 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark409(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent819 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction426(Jurisdiction229): - pass - - -class Disclosure411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction426] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance409(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy409 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem409] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark409] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure411 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem409] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl13(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance409 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent820 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent821 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction427(Jurisdiction229): - pass - - -class Disclosure412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction427] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance410(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy410 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem410] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark410] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure412 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem410] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media26 | Media27, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl13 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance410 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent822 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent823 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction428(Jurisdiction229): - pass - - -class Disclosure413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction428] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance411(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy411 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem411] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark411] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure413 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem411] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance411 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent824 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent825 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction429(Jurisdiction229): - pass - - -class Disclosure414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction429] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance412(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy412 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem412] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark412] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure414 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem412] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance412 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent826 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent827 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction430(Jurisdiction229): - pass - - -class Disclosure415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction430] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance413(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy413 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem413] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark413] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure415 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem413] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets21423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance413 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets2141( - RootModel[ - Assets2142 - | Assets2143 - | Assets2144 - | Assets2145 - | Assets2146 - | Assets2147 - | Assets2148 - | Assets2149 - | Assets21410 - | Assets21411 - | Assets21412 - | Assets21413 - | Assets21414 - | Assets21415 - | Assets160 - | Assets21417 - | Assets21418 - | Assets21419 - | Assets21420 - | Assets21421 - | Assets21422 - | Assets21423 - ] -): - root: Annotated[ - Assets2142 - | Assets2143 - | Assets2144 - | Assets2145 - | Assets2146 - | Assets2147 - | Assets2148 - | Assets2149 - | Assets21410 - | Assets21411 - | Assets21412 - | Assets21413 - | Assets21414 - | Assets21415 - | Assets160 - | Assets21417 - | Assets21418 - | Assets21419 - | Assets21420 - | Assets21421 - | Assets21422 - | Assets21423, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets214(RootModel[list[Assets2141]]): - root: Annotated[list[Assets2141], Field(min_length=1)] - - -class EmbeddedProvenanceItem414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent828 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent829 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction431(Jurisdiction229): - pass - - -class Disclosure416(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction431] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance414(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy414 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem414] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark414] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure416 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem414] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance414 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride74(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo74 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand24(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride74 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right9(Right7): - pass - - -class EmbeddedProvenanceItem415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent830 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent831 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction432(Jurisdiction229): - pass - - -class Disclosure417(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction432] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance415(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy415 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem415] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark415] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure417 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem415] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifests(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets192 - | Assets193 - | Assets194 - | Assets195 - | Assets196 - | Assets197 - | Assets198 - | Assets199 - | Assets200 - | Assets201 - | Assets202 - | Assets203 - | Assets204 - | Assets205 - | Assets206 - | Assets207 - | Assets208 - | Assets209 - | Assets210 - | Assets211 - | Assets212 - | Assets213 - | Assets214, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand24 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right9] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance415 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem416(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent832 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark416(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent833 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction433(Jurisdiction229): - pass - - -class Disclosure418(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction433] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance416(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy416 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem416] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark416] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure418 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem416] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets215(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance416 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem417(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent834 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark417(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent835 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction434(Jurisdiction229): - pass - - -class Disclosure419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction434] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance417(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy417 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem417] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark417] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure419 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem417] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance417 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem418(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent836 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark418(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent837 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction435(Jurisdiction229): - pass - - -class Disclosure420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction435] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance418(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy418 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem418] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark418] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure420 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem418] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance418 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent838 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent839 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction436(Jurisdiction229): - pass - - -class Disclosure421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction436] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance419(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy419 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem419] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark419] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure421 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem419] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets218(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance419 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent840 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent841 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction437(Jurisdiction229): - pass - - -class Disclosure422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction437] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance420(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy420 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem420] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark420] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure422 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem420] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets219(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance420 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent842 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent843 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction438(Jurisdiction229): - pass - - -class Disclosure423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction438] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance421(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy421 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem421] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark421] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure423 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem421] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets220(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance421 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent844 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent845 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction439(Jurisdiction229): - pass - - -class Disclosure424(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction439] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance422(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy422 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem422] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark422] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure424 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem422] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets221(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance422 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent846 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent847 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction440(Jurisdiction229): - pass - - -class Disclosure425(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction440] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance423(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy423 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem423] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark423] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure425 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem423] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets222(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance423 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem424(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent848 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark424(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent849 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction441(Jurisdiction229): - pass - - -class Disclosure426(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction441] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance424(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy424 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem424] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark424] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure426 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem424] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets223(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance424 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem425(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent850 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark425(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent851 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction442(Jurisdiction229): - pass - - -class Disclosure427(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction442] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance425(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy425 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem425] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark425] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure427 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem425] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets224(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance425 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem426(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent852 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark426(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent853 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction443(Jurisdiction229): - pass - - -class Disclosure428(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction443] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance426(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy426 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem426] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark426] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure428 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem426] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance426 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem427(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent854 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark427(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent855 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction444(Jurisdiction229): - pass - - -class Disclosure429(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction444] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance427(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy427 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem427] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark427] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure429 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem427] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets226(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance427 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem428(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent856 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark428(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent857 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction445(Jurisdiction229): - pass - - -class Disclosure430(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction445] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance428(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy428 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem428] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark428] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure430 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem428] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets227(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance428 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem429(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent858 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark429(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent859 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction446(Jurisdiction229): - pass - - -class Disclosure431(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction446] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance429(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy429 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem429] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark429] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure431 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem429] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets228(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance429 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets229(Assets160): - pass - - -class RequiredDisclosure15(RequiredDisclosure): - pass - - -class Compliance15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure15] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets230(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset15] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance15 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets231(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping16] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem430(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent860 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark430(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent861 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction448(Jurisdiction229): - pass - - -class Disclosure432(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction448] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance430(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy430 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem430] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark430] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure432 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem430] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets232(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization14 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance430 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem431(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent862 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark431(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent863 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction449(Jurisdiction229): - pass - - -class Disclosure433(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction449] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance431(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy431 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem431] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark431] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure433 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem431] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media28(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance431 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem432(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent864 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark432(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent865 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction450(Jurisdiction229): - pass - - -class Disclosure434(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction450] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance432(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy432 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem432] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark432] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure434 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem432] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media29(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance432 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem433(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent866 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark433(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent867 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction451(Jurisdiction229): - pass - - -class Disclosure435(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction451] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance433(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy433 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem433] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark433] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure435 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem433] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl14(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance433 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem434(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent868 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark434(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent869 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction452(Jurisdiction229): - pass - - -class Disclosure436(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction452] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance434(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy434 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem434] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark434] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure436 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem434] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets233(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media28 | Media29, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl14 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance434 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem435(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent870 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark435(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent871 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction453(Jurisdiction229): - pass - - -class Disclosure437(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction453] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance435(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy435 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem435] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark435] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure437 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem435] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets234(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance435 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem436(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent872 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark436(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent873 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction454(Jurisdiction229): - pass - - -class Disclosure438(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction454] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance436(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy436 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem436] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark436] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure438 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem436] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets235(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance436 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem437(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent874 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark437(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent875 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction455(Jurisdiction229): - pass - - -class Disclosure439(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction455] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance437(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy437 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem437] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark437] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure439 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem437] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets236(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance437 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem438(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent876 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark438(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent877 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction456(Jurisdiction229): - pass - - -class Disclosure440(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction456] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance438(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy438 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem438] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark438] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure440 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem438] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2372(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance438 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem439(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent878 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark439(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent879 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction457(Jurisdiction229): - pass - - -class Disclosure441(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction457] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance439(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy439 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem439] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark439] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure441 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem439] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2373(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance439 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem440(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent880 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark440(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent881 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction458(Jurisdiction229): - pass - - -class Disclosure442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction458] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance440(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy440 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem440] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark440] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure442 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem440] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2374(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance440 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem441(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent882 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark441(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent883 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction459(Jurisdiction229): - pass - - -class Disclosure443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction459] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance441(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy441 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem441] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark441] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure443 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem441] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2375(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance441 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent884 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent885 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction460(Jurisdiction229): - pass - - -class Disclosure444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction460] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance442(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy442 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem442] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark442] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure444 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem442] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2376(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance442 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent886 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent887 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction461(Jurisdiction229): - pass - - -class Disclosure445(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction461] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance443(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy443 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem443] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark443] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure445 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem443] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2377(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance443 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent888 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent889 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction462(Jurisdiction229): - pass - - -class Disclosure446(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction462] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance444(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy444 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem444] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark444] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure446 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem444] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2378(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance444 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem445(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent890 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark445(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent891 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction463(Jurisdiction229): - pass - - -class Disclosure447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction463] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance445(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy445 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem445] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark445] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure447 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem445] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2379(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance445 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem446(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent892 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark446(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent893 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction464(Jurisdiction229): - pass - - -class Disclosure448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction464] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance446(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy446 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem446] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark446] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure448 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem446] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23710(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance446 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent894 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent895 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction465(Jurisdiction229): - pass - - -class Disclosure449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction465] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance447(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy447 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem447] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark447] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure449 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem447] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23711(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance447 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent896 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent897 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction466(Jurisdiction229): - pass - - -class Disclosure450(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction466] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance448(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy448 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem448] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark448] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure450 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem448] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23712(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance448 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent898 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent899 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction467(Jurisdiction229): - pass - - -class Disclosure451(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction467] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance449(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy449 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem449] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark449] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure451 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem449] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23713(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance449 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem450(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent900 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark450(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent901 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction468(Jurisdiction229): - pass - - -class Disclosure452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction468] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance450(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy450 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem450] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark450] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure452 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem450] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23714(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance450 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem451(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent902 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark451(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent903 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction469(Jurisdiction229): - pass - - -class Disclosure453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction469] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance451(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy451 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem451] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark451] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure453 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem451] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23715(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance451 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure16(RequiredDisclosure): - pass - - -class Compliance16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure16] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets23717(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset16] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance16 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets23718(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping17] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent904 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent905 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction471(Jurisdiction229): - pass - - -class Disclosure454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction471] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance452(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy452 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem452] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark452] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure454 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem452] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23719(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization15 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance452 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent906 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent907 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction472(Jurisdiction229): - pass - - -class Disclosure455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction472] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance453(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy453 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem453] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark453] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure455 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem453] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media30(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance453 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent908 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent909 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction473(Jurisdiction229): - pass - - -class Disclosure456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction473] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance454(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy454 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem454] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark454] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure456 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem454] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media31(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance454 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent910 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent911 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction474(Jurisdiction229): - pass - - -class Disclosure457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction474] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance455(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy455 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem455] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark455] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure457 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem455] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl15(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance455 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent912 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent913 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction475(Jurisdiction229): - pass - - -class Disclosure458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction475] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance456(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy456 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem456] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark456] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure458 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem456] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23720(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media30 | Media31, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl15 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance456 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent914 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent915 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction476(Jurisdiction229): - pass - - -class Disclosure459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction476] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance457(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy457 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem457] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark457] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure459 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem457] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23721(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance457 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent916 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent917 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction477(Jurisdiction229): - pass - - -class Disclosure460(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction477] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance458(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy458 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem458] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark458] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure460 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem458] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23722(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance458 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent918 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent919 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction478(Jurisdiction229): - pass - - -class Disclosure461(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction478] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance459(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy459 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem459] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark459] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure461 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem459] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets23723(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance459 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets2371( - RootModel[ - Assets2372 - | Assets2373 - | Assets2374 - | Assets2375 - | Assets2376 - | Assets2377 - | Assets2378 - | Assets2379 - | Assets23710 - | Assets23711 - | Assets23712 - | Assets23713 - | Assets23714 - | Assets23715 - | Assets160 - | Assets23717 - | Assets23718 - | Assets23719 - | Assets23720 - | Assets23721 - | Assets23722 - | Assets23723 - ] -): - root: Annotated[ - Assets2372 - | Assets2373 - | Assets2374 - | Assets2375 - | Assets2376 - | Assets2377 - | Assets2378 - | Assets2379 - | Assets23710 - | Assets23711 - | Assets23712 - | Assets23713 - | Assets23714 - | Assets23715 - | Assets160 - | Assets23717 - | Assets23718 - | Assets23719 - | Assets23720 - | Assets23721 - | Assets23722 - | Assets23723, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets237(RootModel[list[Assets2371]]): - root: Annotated[list[Assets2371], Field(min_length=1)] - - -class EmbeddedProvenanceItem460(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent920 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark460(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent921 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction479(Jurisdiction229): - pass - - -class Disclosure462(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction479] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance460(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy460 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem460] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark460] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure462 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem460] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance460 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride75(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo75 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand25(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride75 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right10(Right7): - pass - - -class EmbeddedProvenanceItem461(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent922 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark461(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent923 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction480(Jurisdiction229): - pass - - -class Disclosure463(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction480] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance461(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy461 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem461] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark461] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure463 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem461] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifests1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets215 - | Assets216 - | Assets217 - | Assets218 - | Assets219 - | Assets220 - | Assets221 - | Assets222 - | Assets223 - | Assets224 - | Assets225 - | Assets226 - | Assets227 - | Assets228 - | Assets229 - | Assets230 - | Assets231 - | Assets232 - | Assets233 - | Assets234 - | Assets235 - | Assets236 - | Assets237, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand25 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right10] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance461 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Result391(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError22 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig24 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creative_manifests: Annotated[ - list[CreativeManifests | CreativeManifests1], - Field( - description='Array of generated creative manifests, one per requested format. Each manifest contains its own format_id identifying which format it was generated for.', - min_length=1, - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the earliest generated asset URL expires across all manifests. Re-build after this time to get fresh URLs.' - ), - ] = None - preview: Annotated[ - Preview2 | None, - Field( - description='Preview renders included when the request set include_preview to true and the agent supports it. Contains one default preview per requested format. preview_inputs is ignored for multi-format requests.' - ), - ] = None - preview_error: Annotated[ - PreviewError1 | None, - Field( - description='When include_preview was true in the request but preview generation failed. Uses the standard error structure with code, message, and recovery classification. Omitted when preview succeeded or was not requested.', - title='Error', - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate card pricing option was applied for this build. Represents the total cost of the entire multi-format build call. Present when the creative agent charges for its services.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Total cost incurred for this multi-format build, denominated in currency. May be 0 for CPM-priced creatives where cost accrues at serve time.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field(description='ISO 4217 currency code for vendor_cost.', pattern='^[A-Z]{3}$'), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption details for this build. Informational — lets the buyer verify that vendor_cost is consistent with the rate card.', - title='Creative Consumption', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig25(PushNotificationConfig18): - pass - - -class EmbeddedProvenanceItem462(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent924 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark462(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent925 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction481(Jurisdiction229): - pass - - -class Disclosure464(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction481] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance462(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy462 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem462] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark462] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure464 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem462] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets238(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance462 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem463(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent926 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark463(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent927 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction482(Jurisdiction229): - pass - - -class Disclosure465(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction482] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance463(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy463 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem463] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark463] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure465 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem463] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets239(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance463 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem464(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent928 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark464(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent929 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction483(Jurisdiction229): - pass - - -class Disclosure466(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction483] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance464(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy464 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem464] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark464] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure466 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem464] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets240(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance464 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem465(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent930 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark465(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent931 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction484(Jurisdiction229): - pass - - -class Disclosure467(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction484] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance465(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy465 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem465] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark465] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure467 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem465] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets241(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance465 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem466(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent932 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark466(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent933 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction485(Jurisdiction229): - pass - - -class Disclosure468(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction485] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance466(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy466 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem466] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark466] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure468 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem466] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets242(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance466 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem467(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent934 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark467(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent935 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction486(Jurisdiction229): - pass - - -class Disclosure469(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction486] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance467(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy467 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem467] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark467] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure469 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem467] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets243(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance467 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem468(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent936 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark468(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent937 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction487(Jurisdiction229): - pass - - -class Disclosure470(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction487] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance468(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy468 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem468] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark468] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure470 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem468] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets244(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance468 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem469(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent938 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark469(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent939 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction488(Jurisdiction229): - pass - - -class Disclosure471(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction488] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance469(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy469 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem469] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark469] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure471 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem469] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets245(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance469 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem470(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent940 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark470(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent941 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction489(Jurisdiction229): - pass - - -class Disclosure472(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction489] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance470(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy470 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem470] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark470] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure472 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem470] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets246(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance470 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem471(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent942 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark471(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent943 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction490(Jurisdiction229): - pass - - -class Disclosure473(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction490] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance471(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy471 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem471] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark471] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure473 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem471] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets247(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance471 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem472(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent944 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark472(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent945 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction491(Jurisdiction229): - pass - - -class Disclosure474(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction491] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance472(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy472 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem472] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark472] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure474 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem472] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets248(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance472 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem473(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent946 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark473(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent947 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction492(Jurisdiction229): - pass - - -class Disclosure475(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction492] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance473(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy473 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem473] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark473] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure475 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem473] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets249(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance473 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem474(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent948 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark474(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent949 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction493(Jurisdiction229): - pass - - -class Disclosure476(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction493] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance474(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy474 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem474] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark474] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure476 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem474] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets250(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance474 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem475(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent950 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark475(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent951 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction494(Jurisdiction229): - pass - - -class Disclosure477(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction494] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance475(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy475 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem475] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark475] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure477 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem475] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets251(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance475 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets252(Assets160): - pass - - -class RequiredDisclosure17(RequiredDisclosure): - pass - - -class Compliance17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure17] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets253(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset17] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance17 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets254(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping18] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem476(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent952 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark476(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent953 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction496(Jurisdiction229): - pass - - -class Disclosure478(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction496] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance476(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy476 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem476] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark476] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure478 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem476] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets255(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization16 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance476 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem477(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent954 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark477(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent955 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction497(Jurisdiction229): - pass - - -class Disclosure479(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction497] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance477(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy477 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem477] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark477] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure479 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem477] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media32(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance477 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem478(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent956 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark478(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent957 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction498(Jurisdiction229): - pass - - -class Disclosure480(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction498] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance478(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy478 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem478] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark478] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure480 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem478] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media33(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance478 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem479(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent958 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark479(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent959 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction499(Jurisdiction229): - pass - - -class Disclosure481(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction499] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance479(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy479 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem479] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark479] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure481 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem479] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl16(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance479 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem480(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent960 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark480(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent961 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction500(Jurisdiction229): - pass - - -class Disclosure482(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction500] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance480(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy480 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem480] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark480] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure482 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem480] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets256(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media32 | Media33, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl16 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance480 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem481(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent962 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark481(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent963 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction501(Jurisdiction229): - pass - - -class Disclosure483(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction501] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance481(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy481 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem481] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark481] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure483 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem481] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets257(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance481 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem482(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent964 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark482(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent965 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction502(Jurisdiction229): - pass - - -class Disclosure484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction502] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance482(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy482 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem482] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark482] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure484 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem482] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets258(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance482 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem483(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent966 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark483(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent967 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction503(Jurisdiction229): - pass - - -class Disclosure485(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction503] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance483(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy483 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem483] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark483] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure485 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem483] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets259(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance483 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent968 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent969 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction504(Jurisdiction229): - pass - - -class Disclosure486(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction504] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy484 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem484] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark484] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure486 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem484] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2602(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance484 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem485(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent970 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark485(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent971 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction505(Jurisdiction229): - pass - - -class Disclosure487(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction505] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance485(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy485 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem485] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark485] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure487 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem485] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2603(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance485 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem486(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent972 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark486(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent973 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction506(Jurisdiction229): - pass - - -class Disclosure488(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction506] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance486(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy486 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem486] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark486] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure488 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem486] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2604(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance486 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem487(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent974 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark487(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent975 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction507(Jurisdiction229): - pass - - -class Disclosure489(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction507] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance487(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy487 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem487] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark487] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure489 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem487] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2605(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance487 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem488(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent976 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark488(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent977 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction508(Jurisdiction229): - pass - - -class Disclosure490(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction508] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance488(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy488 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem488] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark488] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure490 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem488] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2606(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance488 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem489(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent978 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark489(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent979 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction509(Jurisdiction229): - pass - - -class Disclosure491(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction509] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance489(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy489 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem489] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark489] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure491 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem489] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2607(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance489 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem490(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent980 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark490(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent981 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction510(Jurisdiction229): - pass - - -class Disclosure492(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction510] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance490(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy490 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem490] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark490] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure492 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem490] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2608(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance490 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem491(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent982 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark491(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent983 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction511(Jurisdiction229): - pass - - -class Disclosure493(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction511] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance491(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy491 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem491] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark491] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure493 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem491] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2609(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance491 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem492(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent984 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark492(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent985 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction512(Jurisdiction229): - pass - - -class Disclosure494(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction512] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance492(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy492 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem492] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark492] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure494 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem492] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26010(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance492 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem493(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent986 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark493(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent987 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction513(Jurisdiction229): - pass - - -class Disclosure495(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction513] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance493(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy493 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem493] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark493] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure495 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem493] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26011(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance493 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem494(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent988 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark494(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent989 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction514(Jurisdiction229): - pass - - -class Disclosure496(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction514] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance494(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy494 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem494] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark494] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure496 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem494] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26012(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance494 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem495(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent990 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark495(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent991 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction515(Jurisdiction229): - pass - - -class Disclosure497(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction515] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance495(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy495 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem495] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark495] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure497 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem495] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26013(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance495 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem496(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent992 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark496(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent993 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction516(Jurisdiction229): - pass - - -class Disclosure498(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction516] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance496(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy496 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem496] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark496] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure498 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem496] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26014(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance496 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem497(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent994 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark497(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent995 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction517(Jurisdiction229): - pass - - -class Disclosure499(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction517] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance497(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy497 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem497] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark497] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure499 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem497] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26015(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance497 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure18(RequiredDisclosure): - pass - - -class Compliance18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure18] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets26017(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset18] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance18 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets26018(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping19] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem498(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent996 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark498(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent997 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction519(Jurisdiction229): - pass - - -class Disclosure500(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction519] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance498(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy498 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem498] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark498] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure500 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem498] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26019(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization17 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance498 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem499(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent998 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark499(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent999 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction520(Jurisdiction229): - pass - - -class Disclosure501(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction520] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance499(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy499 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem499] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark499] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure501 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem499] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media34(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance499 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem500(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1000 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark500(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1001 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction521(Jurisdiction229): - pass - - -class Disclosure502(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction521] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance500(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy500 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem500] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark500] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure502 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem500] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media35(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance500 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem501(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1002 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark501(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1003 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction522(Jurisdiction229): - pass - - -class Disclosure503(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction522] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance501(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy501 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem501] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark501] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure503 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem501] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl17(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance501 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem502(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1004 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark502(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1005 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction523(Jurisdiction229): - pass - - -class Disclosure504(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction523] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance502(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy502 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem502] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark502] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure504 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem502] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26020(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media34 | Media35, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl17 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance502 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem503(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1006 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark503(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1007 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction524(Jurisdiction229): - pass - - -class Disclosure505(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction524] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance503(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy503 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem503] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark503] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure505 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem503] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26021(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance503 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem504(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1008 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark504(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1009 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction525(Jurisdiction229): - pass - - -class Disclosure506(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction525] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance504(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy504 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem504] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark504] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure506 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem504] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26022(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance504 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem505(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1010 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark505(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1011 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction526(Jurisdiction229): - pass - - -class Disclosure507(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction526] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance505(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy505 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem505] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark505] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure507 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem505] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets26023(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance505 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets2601( - RootModel[ - Assets2602 - | Assets2603 - | Assets2604 - | Assets2605 - | Assets2606 - | Assets2607 - | Assets2608 - | Assets2609 - | Assets26010 - | Assets26011 - | Assets26012 - | Assets26013 - | Assets26014 - | Assets26015 - | Assets160 - | Assets26017 - | Assets26018 - | Assets26019 - | Assets26020 - | Assets26021 - | Assets26022 - | Assets26023 - ] -): - root: Annotated[ - Assets2602 - | Assets2603 - | Assets2604 - | Assets2605 - | Assets2606 - | Assets2607 - | Assets2608 - | Assets2609 - | Assets26010 - | Assets26011 - | Assets26012 - | Assets26013 - | Assets26014 - | Assets26015 - | Assets160 - | Assets26017 - | Assets26018 - | Assets26019 - | Assets26020 - | Assets26021 - | Assets26022 - | Assets26023, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets260(RootModel[list[Assets2601]]): - root: Annotated[list[Assets2601], Field(min_length=1)] - - -class EmbeddedProvenanceItem506(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1012 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark506(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1013 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction527(Jurisdiction229): - pass - - -class Disclosure508(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction527] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance506(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy506 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem506] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark506] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure508 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem506] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance506 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride76(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo76 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand26(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride76 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right11(Right7): - pass - - -class EmbeddedProvenanceItem507(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1014 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark507(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1015 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction528(Jurisdiction229): - pass - - -class Disclosure509(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction528] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance507(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy507 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem507] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark507] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure509 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem507] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest7(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] - format_kind: CanonicalFormatKind | None = None - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets238 - | Assets239 - | Assets240 - | Assets241 - | Assets242 - | Assets243 - | Assets244 - | Assets245 - | Assets246 - | Assets247 - | Assets248 - | Assets249 - | Assets250 - | Assets251 - | Assets252 - | Assets253 - | Assets254 - | Assets255 - | Assets256 - | Assets257 - | Assets258 - | Assets259 - | Assets260, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand26 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right11] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance507 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class EmbeddedProvenanceItem508(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1016 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark508(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1017 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction529(Jurisdiction229): - pass - - -class Disclosure510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction529] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance508(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy508 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem508] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark508] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure510 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem508] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets261(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance508 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem509(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1018 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark509(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1019 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction530(Jurisdiction229): - pass - - -class Disclosure511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction530] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance509(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy509 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem509] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark509] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure511 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem509] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets262(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance509 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1020 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1021 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction531(Jurisdiction229): - pass - - -class Disclosure512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction531] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance510(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy510 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem510] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark510] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure512 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem510] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets263(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance510 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1022 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1023 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction532(Jurisdiction229): - pass - - -class Disclosure513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction532] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance511(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy511 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem511] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark511] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure513 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem511] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets264(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance511 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1024 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1025 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction533(Jurisdiction229): - pass - - -class Disclosure514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction533] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance512(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy512 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem512] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark512] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure514 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem512] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets265(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance512 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1026 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1027 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction534(Jurisdiction229): - pass - - -class Disclosure515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction534] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance513(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy513 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem513] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark513] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure515 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem513] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets266(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance513 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1028 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1029 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction535(Jurisdiction229): - pass - - -class Disclosure516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction535] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance514(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy514 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem514] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark514] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure516 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem514] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets267(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance514 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1030 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1031 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction536(Jurisdiction229): - pass - - -class Disclosure517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction536] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance515(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy515 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem515] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark515] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure517 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem515] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets268(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance515 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1032 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1033 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction537(Jurisdiction229): - pass - - -class Disclosure518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction537] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance516(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy516 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem516] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark516] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure518 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem516] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets269(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance516 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1034 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1035 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction538(Jurisdiction229): - pass - - -class Disclosure519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction538] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance517(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy517 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem517] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark517] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure519 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem517] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets270(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance517 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1036 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1037 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction539(Jurisdiction229): - pass - - -class Disclosure520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction539] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance518(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy518 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem518] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark518] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure520 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem518] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets271(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance518 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1038 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1039 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction540(Jurisdiction229): - pass - - -class Disclosure521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction540] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance519(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy519 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem519] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark519] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure521 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem519] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets272(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance519 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1040 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1041 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction541(Jurisdiction229): - pass - - -class Disclosure522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction541] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance520(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy520 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem520] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark520] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure522 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem520] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets273(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance520 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1042 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1043 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction542(Jurisdiction229): - pass - - -class Disclosure523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction542] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance521(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy521 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem521] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark521] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure523 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem521] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets274(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance521 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class Assets275(Assets160): - pass - - -class RequiredDisclosure19(RequiredDisclosure): - pass - - -class Compliance19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure19] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets276(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset19] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance19 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets277(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping20] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1044 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1045 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction544(Jurisdiction229): - pass - - -class Disclosure524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction544] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance522(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy522 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem522] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark522] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure524 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem522] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets278(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization18 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance522 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1046 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1047 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction545(Jurisdiction229): - pass - - -class Disclosure525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction545] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance523(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy523 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem523] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark523] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure525 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem523] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media36(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance523 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1048 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1049 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction546(Jurisdiction229): - pass - - -class Disclosure526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction546] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance524(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy524 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem524] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark524] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure526 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem524] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media37(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance524 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1050 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1051 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction547(Jurisdiction229): - pass - - -class Disclosure527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction547] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance525(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy525 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem525] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark525] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure527 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem525] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl18(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance525 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1052 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1053 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction548(Jurisdiction229): - pass - - -class Disclosure528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction548] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance526(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy526 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem526] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark526] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure528 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem526] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets279(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media36 | Media37, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl18 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance526 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1054 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1055 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction549(Jurisdiction229): - pass - - -class Disclosure529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction549] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance527(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy527 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem527] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark527] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure529 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem527] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets280(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance527 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1056 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1057 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction550(Jurisdiction229): - pass - - -class Disclosure530(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction550] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance528(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy528 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem528] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark528] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure530 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem528] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets281(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance528 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1058 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1059 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction551(Jurisdiction229): - pass - - -class Disclosure531(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction551] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance529(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy529 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem529] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark529] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure531 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem529] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets282(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance529 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem530(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1060 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark530(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1061 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction552(Jurisdiction229): - pass - - -class Disclosure532(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction552] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance530(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy530 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem530] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark530] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure532 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem530] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2832(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance530 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem531(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1062 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark531(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1063 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction553(Jurisdiction229): - pass - - -class Disclosure533(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction553] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance531(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy531 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem531] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark531] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure533 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem531] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance531 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem532(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1064 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark532(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1065 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction554(Jurisdiction229): - pass - - -class Disclosure534(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction554] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance532(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy532 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem532] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark532] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure534 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem532] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2834(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['audio'], - Field( - description='Discriminator identifying this as an audio asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'audio' - url: Annotated[AnyUrl, Field(description='URL to the audio asset')] - duration_ms: Annotated[ - int | None, Field(description='Audio duration in milliseconds', ge=0) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, - Field(description='Audio container/file format (mp3, m4a, aac, wav, ogg, flac, etc.)'), - ] = None - codec: Annotated[ - str | None, - Field( - description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, vorbis, opus, flac, ac3, eac3, etc.)' - ), - ] = None - sampling_rate_hz: Annotated[ - int | None, Field(description='Sampling rate in Hz (e.g., 44100, 48000, 96000)') - ] = None - channels: AudioChannelLayout | None = None - bit_depth: Annotated[AudioBitDepth | None, Field(description='Bit depth')] = None - bitrate_kbps: Annotated[ - int | None, Field(description='Bitrate in kilobits per second', ge=1) - ] = None - loudness_lufs: Annotated[float | None, Field(description='Integrated loudness in LUFS')] = None - true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance532 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem533(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1066 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark533(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1067 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction555(Jurisdiction229): - pass - - -class Disclosure535(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction555] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance533(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy533 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem533] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark533] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure535 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem533] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance533 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating VAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns VAST XML. May carry unsubstituted ad-server macros — VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem534(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1068 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark534(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1069 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction556(Jurisdiction229): - pass - - -class Disclosure536(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction556] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance534(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy534 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem534] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark534] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure536 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem534] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2836(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast'], - Field( - description='Discriminator identifying this as a VAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast' - vast_version: VastVersion | None = None - vpaid_enabled: Annotated[ - bool | None, - Field(description='Whether VPAID (Video Player-Ad Interface Definition) is supported'), - ] = None - duration_ms: Annotated[ - int | None, Field(description='Expected video duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[VASTTrackingEvent] | None, - Field(description='Tracking events supported by this VAST tag'), - ] = None - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance534 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating VAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline VAST XML content')] - - -class EmbeddedProvenanceItem535(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1070 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark535(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1071 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction557(Jurisdiction229): - pass - - -class Disclosure537(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction557] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance535(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy535 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem535] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark535] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure537 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem535] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2837(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['text'], - Field( - description='Discriminator identifying this as a text asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'text' - content: Annotated[str, Field(description='Text content')] - language: Annotated[str | None, Field(description="Language code (e.g., 'en', 'es', 'fr')")] = ( - None - ) - provenance: Annotated[ - Provenance535 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem536(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1072 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark536(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1073 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction558(Jurisdiction229): - pass - - -class Disclosure538(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction558] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance536(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy536 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem536] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark536] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure538 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem536] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2838(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance536 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem537(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1074 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark537(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1075 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction559(Jurisdiction229): - pass - - -class Disclosure539(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction559] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance537(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy537 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem537] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark537] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure539 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem537] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets2839(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['html'], - Field( - description='Discriminator identifying this as an HTML asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'html' - content: Annotated[str, Field(description='HTML content')] - version: Annotated[str | None, Field(description="HTML version (e.g., 'HTML5')")] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance537 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem538(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1076 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark538(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1077 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction560(Jurisdiction229): - pass - - -class Disclosure540(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction560] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance538(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy538 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem538] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark538] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure540 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem538] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28310(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['javascript'], - Field( - description='Discriminator identifying this as a JavaScript asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'javascript' - content: Annotated[str, Field(description='JavaScript content')] - module_type: JavaScriptModuleType | None = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance538 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem539(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1078 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark539(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1079 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction561(Jurisdiction229): - pass - - -class Disclosure541(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction561] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance539(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy539 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem539] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark539] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure541 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem539] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28311(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['zip'], - Field( - description='Discriminator identifying this as a zip-bundled asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'zip' - url: Annotated[AnyUrl, Field(description='URL where the zip archive is hosted. Must be HTTPS.')] - max_file_size_kb: Annotated[ - int | None, - Field( - description='Maximum file size in kilobytes. Receivers should reject zips exceeding this.', - ge=0, - ), - ] = None - entry_point: Annotated[ - str | None, - Field( - description="Relative path to the entry file within the zip (typically 'index.html'). Receivers default to 'index.html' if absent." - ), - ] = None - allowed_inner_extensions: Annotated[ - list[str] | None, - Field( - description="File extensions permitted inside the zip (e.g., ['html', 'css', 'js', 'png', 'jpg', 'svg', 'webp', 'json', 'woff2']). Receivers may reject zips containing other extensions." - ), - ] = None - backup_image_url: Annotated[ - AnyUrl | None, - Field( - description='Fallback image URL for environments that cannot render the bundled creative (e.g., non-HTML5 endpoints, ad blockers). Recommended for HTML5 banners.' - ), - ] = None - digest: Annotated[ - str | None, - Field( - description='Optional SHA-256 content digest of the zip archive (sha256:) for integrity verification. Lets receivers detect tampered or stale archives.', - pattern='^sha256:[a-f0-9]{64}$', - ), - ] = None - accessibility: Annotated[ - Accessibility | None, - Field(description='Self-declared accessibility properties for this opaque creative'), - ] = None - provenance: Annotated[ - Provenance539 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem540(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1080 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark540(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1081 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction562(Jurisdiction229): - pass - - -class Disclosure542(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction562] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance540(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy540 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem540] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark540] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure542 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem540] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28312(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['webhook'], - Field( - description='Discriminator identifying this as a webhook asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'webhook' - url: Annotated[AnyUrl, Field(description='Webhook URL to call for dynamic content')] - method: HTTPMethod | None = HTTPMethod.POST - timeout_ms: Annotated[ - int | None, - Field(description='Maximum time to wait for response in milliseconds', ge=10, le=5000), - ] = 500 - supported_macros: Annotated[ - list[UniversalMacro | str] | None, - Field( - description='Universal macros that can be passed to webhook (e.g., DEVICE_TYPE, COUNTRY). See docs/creative/universal-macros.mdx for full list.' - ), - ] = None - required_macros: Annotated[ - list[UniversalMacro | str] | None, - Field(description='Universal macros that must be provided for webhook to function'), - ] = None - response_type: WebhookResponseType - security: Annotated[Security, Field(description='Security configuration for webhook calls')] - provenance: Annotated[ - Provenance540 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem541(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1082 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark541(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1083 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction563(Jurisdiction229): - pass - - -class Disclosure543(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction563] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance541(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy541 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem541] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark541] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure543 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem541] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28313(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['css'], - Field( - description='Discriminator identifying this as a CSS asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'css' - content: Annotated[str, Field(description='CSS content')] - media: Annotated[ - str | None, Field(description="CSS media query context (e.g., 'screen', 'print')") - ] = None - provenance: Annotated[ - Provenance541 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem542(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1084 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark542(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1085 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction564(Jurisdiction229): - pass - - -class Disclosure544(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction564] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance542(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy542 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem542] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark542] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure544 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem542] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28314(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance542 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['url'], - Field(description='Discriminator indicating DAAST is delivered via URL endpoint'), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL endpoint that returns DAAST XML. May carry unsubstituted ad-server macros — DAAST/VAST-style `[MACRO]` and `${MACRO}` placeholders are accepted as-is (RFC 6570 syntax); buyers MUST NOT pre-encode macro delimiters, since players match the literal token at substitution time.' - ), - ] - - -class EmbeddedProvenanceItem543(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1086 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark543(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1087 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction565(Jurisdiction229): - pass - - -class Disclosure545(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction565] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance543(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy543 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem543] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark543] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure545 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem543] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28315(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast'], - Field( - description='Discriminator identifying this as a DAAST asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast' - daast_version: DaastVersion | None = None - duration_ms: Annotated[ - int | None, Field(description='Expected audio duration in milliseconds (if known)', ge=0) - ] = None - tracking_events: Annotated[ - list[DAASTTrackingEvent] | None, - Field(description='Tracking events supported by this DAAST tag'), - ] = None - companion_ads: Annotated[ - bool | None, Field(description='Whether companion display ads are included') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the audio content') - ] = None - provenance: Annotated[ - Provenance543 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - delivery_type: Annotated[ - Literal['inline'], - Field(description='Discriminator indicating DAAST is delivered as inline XML content'), - ] = 'inline' - content: Annotated[str, Field(description='Inline DAAST XML content')] - - -class RequiredDisclosure20(RequiredDisclosure): - pass - - -class Compliance20(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required_disclosures: Annotated[ - list[RequiredDisclosure20] | None, - Field( - description='Disclosures that must appear in creatives for this campaign. Each disclosure specifies the text, where it should appear, and which jurisdictions require it.', - min_length=1, - ), - ] = None - prohibited_claims: Annotated[ - list[str] | None, - Field( - description='Claims that must not appear in creatives for this campaign. Creative agents should ensure generated content avoids these claims.', - min_length=1, - ), - ] = None - - -class Assets28317(AdCPBaseModel): - name: Annotated[str, Field(description='Campaign or flight name for identification')] - objective: Annotated[ - Objective | None, - Field( - description='Campaign objective that guides creative tone and call-to-action strategy' - ), - ] = None - tone: Annotated[ - str | None, - Field( - description="Desired tone for this campaign, modulating the brand's base tone (e.g., 'playful and festive', 'premium and aspirational')" - ), - ] = None - audience: Annotated[ - str | None, Field(description='Target audience description for this campaign') - ] = None - territory: Annotated[ - str | None, - Field(description='Creative territory or positioning the campaign should occupy'), - ] = None - messaging: Annotated[ - Messaging | None, Field(description='Messaging framework for the campaign') - ] = None - reference_assets: Annotated[ - list[ReferenceAsset20] | None, - Field( - description='Visual and strategic reference materials such as mood boards, product shots, example creatives, and strategy documents' - ), - ] = None - compliance: Annotated[ - Compliance20 | None, - Field( - description='Regulatory and legal compliance requirements for this campaign. Campaign-specific, regional, and product-based — distinct from brand-level disclaimers in brand.json.' - ), - ] = None - asset_type: Annotated[ - Literal['brief'], - Field( - description='Discriminator identifying this as a brief asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'brief' - - -class Assets28318(AdCPBaseModel): - catalog_id: Annotated[ - str | None, - Field( - description="Buyer's identifier for this catalog. Required when syncing via sync_catalogs. When used in creatives, references a previously synced catalog on the account." - ), - ] = None - name: Annotated[ - str | None, - Field( - description="Human-readable name for this catalog (e.g., 'Summer Products 2025', 'Amsterdam Store Locations')." - ), - ] = None - type: CatalogType - url: Annotated[ - AnyUrl | None, - Field( - description="URL to an external catalog feed. The platform fetches and resolves items from this URL. For offering-type catalogs, the feed contains an array of Offering objects. For other types, the feed format is determined by feed_format. When omitted with type 'product', the platform uses its synced copy of the brand's product catalog." - ), - ] = None - feed_format: FeedFormat | None = None - update_frequency: UpdateFrequency | None = None - items: Annotated[ - list[dict[str, Any]] | None, - Field( - description="Inline catalog data. The item schema depends on the catalog type: Offering objects for 'offering', StoreItem for 'store', HotelItem for 'hotel', FlightItem for 'flight', JobItem for 'job', VehicleItem for 'vehicle', RealEstateItem for 'real_estate', EducationItem for 'education', DestinationItem for 'destination', AppItem for 'app', or freeform objects for 'product', 'inventory', and 'promotion'. Mutually exclusive with url — provide one or the other, not both. Implementations should validate items against the type-specific schema.", - min_length=1, - ), - ] = None - ids: Annotated[ - list[str] | None, - Field( - description='Filter catalog to specific item IDs. For offering-type catalogs, these are offering_id values. For product-type catalogs, these are SKU identifiers.', - min_length=1, - ), - ] = None - gtins: Annotated[ - list[Gtin] | None, - Field( - description="Filter product-type catalogs by GTIN identifiers for cross-retailer catalog matching. Accepts standard GTIN formats (GTIN-8, UPC-A/GTIN-12, EAN-13/GTIN-13, GTIN-14). Only applicable when type is 'product'.", - min_length=1, - ), - ] = None - tags: Annotated[ - list[str] | None, - Field( - description='Filter catalog to items with these tags. Tags are matched using OR logic — items matching any tag are included.', - min_length=1, - ), - ] = None - category: Annotated[ - str | None, - Field( - description="Filter catalog to items in this category (e.g., 'beverages/soft-drinks', 'chef-positions')." - ), - ] = None - query: Annotated[ - str | None, - Field( - description="Natural language filter for catalog items (e.g., 'all pasta sauces under $5', 'amsterdam vacancies')." - ), - ] = None - conversion_events: Annotated[ - list[EventType] | None, - Field( - description="Event types that represent conversions for items in this catalog. Declares what events the platform should attribute to catalog items — e.g., a job catalog converts via submit_application, a product catalog via purchase. The event's content_ids field carries the item IDs that connect back to catalog items. Use content_id_type to declare what identifier type content_ids values represent.", - min_length=1, - ), - ] = None - content_id_type: ContentIDType | None = None - feed_field_mappings: Annotated[ - list[FeedFieldMapping21] | None, - Field( - description='Declarative normalization rules for external feeds. Maps non-standard feed field names, date formats, price encodings, and image URLs to the AdCP catalog item schema. Applied during sync_catalogs ingestion. Supports field renames, named transforms (date, divide, boolean, split), static literal injection, and assignment of image URLs to typed asset pools.', - min_length=1, - ), - ] = None - asset_type: Annotated[ - Literal['catalog'], - Field( - description='Discriminator identifying this as a catalog asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'catalog' - - -class EmbeddedProvenanceItem544(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1088 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark544(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1089 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction567(Jurisdiction229): - pass - - -class Disclosure546(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction567] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance544(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy544 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem544] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark544] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure546 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem544] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28319(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['published_post'], - Field( - description='Discriminator identifying this as a published-post reference asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'published_post' - post_url: Annotated[ - AnyUrl | None, - Field( - description='Canonical URL for the published post. Preferred when the platform exposes a stable public or authenticated URL.' - ), - ] = None - platform: Annotated[ - str | None, - Field( - description="Optional platform or publisher namespace for the referenced post. Informational unless the seller's product declaration or platform extension narrows the accepted values." - ), - ] = None - platform_post_id: Annotated[ - str | None, - Field( - description='Optional platform-native post identifier when a URL alone is not stable or not available. Buyers SHOULD include `platform` when using `platform_post_id` without `post_url`, unless the product or format declaration already narrows the platform. Platform-specific identifier semantics belong in platform_extensions; this field is only an opaque reference.' - ), - ] = None - identity_ref: Annotated[ - IdentityRef | None, - Field( - description='Optional identity hint for the authoring handle/page/channel that owns the post. Sellers MUST verify authorization from platform state; buyers MUST NOT use this object as proof of authorization.' - ), - ] = None - published_at: Annotated[ - AwareDatetime | None, - Field(description='When the referenced post was originally published, if known.'), - ] = None - reference_authorization: Annotated[ - ReferenceAuthorization19 | None, - Field( - description='Server-emitted, seller-observed authorization state for the referenced post or identity. Sellers MAY return this object on read surfaces. On write requests, sellers MUST ignore buyer-supplied `reference_authorization.status` and other authorization-state claims unless a platform extension explicitly defines a signed proof shape and the seller verifies that proof.' - ), - ] = None - provenance: Annotated[ - Provenance544 | None, - Field( - description='Provenance metadata for this reference asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem545(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1090 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark545(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1091 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction568(Jurisdiction229): - pass - - -class Disclosure547(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction568] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance545(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy545 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem545] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark545] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure547 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem545] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media38(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance545 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem546(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1092 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark546(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1093 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction569(Jurisdiction229): - pass - - -class Disclosure548(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction569] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance546(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy546 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem546] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark546] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure548 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem546] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Media39(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['video'], - Field( - description='Discriminator identifying this as a video asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'video' - url: Annotated[AnyUrl, Field(description='URL to the video asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - duration_ms: Annotated[ - int | None, Field(description='Video duration in milliseconds', ge=1) - ] = None - file_size_bytes: Annotated[int | None, Field(description='File size in bytes', ge=1)] = None - container_format: Annotated[ - str | None, Field(description='Video container format (mp4, webm, mov, etc.)') - ] = None - video_codec: Annotated[ - str | None, Field(description='Video codec used (h264, h265, vp9, av1, prores, etc.)') - ] = None - video_bitrate_kbps: Annotated[ - int | None, Field(description='Video stream bitrate in kilobits per second', ge=1) - ] = None - frame_rate: Annotated[ - str | None, - Field( - description="Frame rate as string to preserve precision (e.g., '23.976', '29.97', '30')" - ), - ] = None - frame_rate_type: FrameRateType | None = None - scan_type: ScanType | None = None - color_space: Annotated[ColorSpace | None, Field(description='Color space of the video')] = None - hdr_format: Annotated[ - HdrFormat | None, - Field(description="HDR format if applicable, or 'sdr' for standard dynamic range"), - ] = None - chroma_subsampling: Annotated[ - ChromaSubsampling | None, Field(description='Chroma subsampling format') - ] = None - video_bit_depth: Annotated[VideoBitDepth | None, Field(description='Video bit depth')] = None - gop_interval_seconds: Annotated[ - float | None, Field(description='GOP/keyframe interval in seconds') - ] = None - gop_type: GOPType | None = None - moov_atom_position: MoovAtomPosition | None = None - has_audio: Annotated[ - bool | None, Field(description='Whether the video contains an audio track') - ] = None - audio_codec: Annotated[ - str | None, - Field(description='Audio codec used (aac, aac_lc, he_aac, pcm, mp3, ac3, eac3, etc.)'), - ] = None - audio_sampling_rate_hz: Annotated[ - int | None, Field(description='Audio sampling rate in Hz (e.g., 44100, 48000)') - ] = None - audio_channels: AudioChannelLayout | None = None - audio_bit_depth: Annotated[AudioBitDepth | None, Field(description='Audio bit depth')] = None - audio_bitrate_kbps: Annotated[ - int | None, Field(description='Audio bitrate in kilobits per second', ge=1) - ] = None - audio_loudness_lufs: Annotated[ - float | None, Field(description='Integrated loudness in LUFS') - ] = None - audio_true_peak_dbfs: Annotated[float | None, Field(description='True peak level in dBFS')] = ( - None - ) - captions_url: Annotated[ - AnyUrl | None, Field(description='URL to captions file (WebVTT, SRT, etc.)') - ] = None - transcript_url: Annotated[ - AnyUrl | None, Field(description='URL to text transcript of the video content') - ] = None - audio_description_url: Annotated[ - AnyUrl | None, - Field(description='URL to audio description track for visually impaired users'), - ] = None - provenance: Annotated[ - Provenance546 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem547(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1094 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark547(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1095 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction570(Jurisdiction229): - pass - - -class Disclosure549(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction570] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance547(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy547 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem547] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark547] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure549 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem547] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class LandingPageUrl19(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['url'], - Field( - description='Discriminator identifying this as a URL asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'url' - url: Annotated[ - str, - Field( - description='URL reference. May be a plain URI or an RFC 6570 URI template carrying AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`). Buyers MUST NOT pre-encode macro braces at sync time; the ad server URL-encodes substituted values at impression time. See docs/creative/universal-macros.mdx.' - ), - ] - url_type: URLAssetType | None = None - description: Annotated[ - str | None, Field(description='Description of what this URL points to') - ] = None - provenance: Annotated[ - Provenance547 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem548(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1096 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark548(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1097 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction571(Jurisdiction229): - pass - - -class Disclosure550(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction571] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance548(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy548 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem548] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark548] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure550 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem548] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28320(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['card'], - Field( - description='Discriminator identifying this as a card asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'card' - media: Annotated[ - Media38 | Media39, - Field( - description="The card's primary visual asset. Either an `image` or `video` asset, matching the parent format's `allowed_card_media_asset_types` parameter.", - discriminator='asset_type', - ), - ] - headline: Annotated[ - str | None, - Field( - description='Optional per-card short text label (typically 25-40 chars). Length governed by `card_headline_max_chars` on the format declaration. Meta carousel headline, Pinterest pin title, Snap Collection sticker text, TikTok caption-short.' - ), - ] = None - description: Annotated[ - str | None, - Field( - description='Optional per-card longer text (typically 100-500 chars). Distinct from `headline`: `description` is body copy, `headline` is the label. Length governed by `card_description_max_chars` on the format declaration. Meta carousel description, Pinterest pin description, AI-surface result body text, TikTok long caption.' - ), - ] = None - cta: Annotated[ - str | None, - Field( - description="Optional per-card call-to-action label (e.g., 'SHOP_NOW', 'LEARN_MORE'). When the parent format declares `cta_values` (allowed CTA labels), the per-card `cta` MUST be one of those values. Lets a Meta or TikTok carousel show different CTAs per card." - ), - ] = None - landing_page_url: Annotated[ - LandingPageUrl19 | None, - Field( - description='Optional per-card click-through URL. URL asset with `url_type: "clickthrough"`.', - title='URL Asset', - ), - ] = None - platform_extensions: Annotated[ - list[PlatformExtension] | None, - Field( - description='Per-card platform-specific extensions (URI+digest references). Same hosting model as format-level platform_extensions. Use this for Meta carousel-card attributes, Pinterest pin overrides, etc. — NEVER inline non-canonical keys on the card object directly.' - ), - ] = None - provenance: Annotated[ - Provenance548 | None, - Field( - description='Provenance metadata for this card, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem549(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1098 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark549(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1099 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction572(Jurisdiction229): - pass - - -class Disclosure551(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction572] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance549(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy549 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem549] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark549] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure551 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem549] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28321(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['pixel_tracker'], - Field( - description='Discriminator identifying this as a renderer-fired pixel tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'pixel_tracker' - event: Annotated[ - Event, - Field( - description="Which event this tracker fires on. Event enum mirrors IAB OpenRTB Native 1.2 event-tracker registry (event types 1, 2, 3, 4, 500); the events themselves are generic web-pixel measurement events that apply to any renderer:\n- `impression` (IAB type 1) — fires when the ad is served. Covers both `imptrackers[]` and `jstracker` from the IAB shape, distinguished by `method`.\n- `viewable_mrc_50` (IAB type 2) — IAB MRC viewable, 50% pixels for ≥1 second.\n- `viewable_mrc_100` (IAB type 3) — IAB MRC viewable, 100% pixels for ≥1 second.\n- `viewable_video_50` (IAB type 4) — video-specific viewable, 50% pixels for ≥2 seconds with audio on. On video_hosted; ignored on image/html5.\n- `audible_video_complete` (IAB type 500) — video reached 100% completion with audio on. Distinct from `viewable_video_50` (50% pixels + 2s threshold) — this is the full-completion audible-view event. Meaningful on non-VAST video formats (Meta Reels, YouTube Shorts, TikTok Spark) where audible-complete is a measured event but VAST `` isn't the wire format; VAST formats use `vast_tracker` with `vast_event: complete` plus a separate audible tracker instead.\n- `click` — fires when the user clicks the creative (`link.clicktrackers[]`).\n- `custom` — adopter-defined event for anything not in the standardized enum. MUST also set `custom_event_name`. Reserved for IAB Native event types 555+ (exchange-specific) and any vendor-defined event not yet promoted to a first-class enum value." - ), - ] - method: Annotated[ - Method19 | None, - Field( - description="How the tracker URL is invoked at serve time:\n- `img` — fired as an image pixel (HTTP GET with ``-like semantics; no JS execution)\n- `js` — fired as a script include (renderer evaluates the URL's response as JavaScript)\n\nMatches IAB OpenRTB Native 1.2 method enum (1=img, 2=js). `js` MUST only be used by sellers whose renderer supports JavaScript trackers; sellers without JS-tracker support MUST reject `method: js` declarations at sync_creatives time with `CREATIVE_REJECTED` carrying the reason." - ), - ] = Method19.img - url: Annotated[ - str, - Field( - description="Tracker URL fired when `event` occurs. May carry AdCP universal macros (e.g., `{MEDIA_BUY_ID}`, `{CREATIVE_ID}`, `{CACHEBUSTER}`); the seller's renderer URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx." - ), - ] - custom_event_name: Annotated[ - str | None, - Field( - description='REQUIRED when `event` is `custom`; otherwise MUST be absent. Adopter-defined event name. Sellers without registered handling for a given custom_event_name MUST silently no-op (do not fire) rather than reject — custom events are forward-compatible probes.' - ), - ] = None - provenance: Annotated[ - Provenance549 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem550(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1100 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark550(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1101 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction573(Jurisdiction229): - pass - - -class Disclosure552(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction573] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance550(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy550 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem550] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark550] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure552 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem550] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28322(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['vast_tracker'], - Field( - description='Discriminator identifying this as a VAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'vast_tracker' - vast_event: Annotated[ - VASTTrackingEvent, - Field( - description='The VAST tracking event this URL fires on. Maps 1:1 to the VAST `Tracking event="..."` attribute inside `TrackingEvents`. MUST NOT be `impression` (belongs in the VAST `Impression` element — model as a `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (belong in `VideoClicks`), `error` (VAST `Error` element), or any of `viewable` / `notViewable` / `viewUndetermined` / `measurableImpression` / `viewableImpression` (children of the VAST `ViewableImpression` element, not `TrackingEvents`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `vast_event` occurs. May carry AdCP universal macros (e.g., `{SKU}`, `{MEDIA_BUY_ID}`); the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='VAST `offset` attribute. Required when `vast_event` is `progress`; ignored otherwise. Format matches the VAST 4.2 XSD `Tracking@offset` pattern: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted — the VAST 4.2 XSD pattern does not allow a leading minus.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target31 | None, - Field( - description='Which VAST creative element this tracker scopes to — `linear` for `/`, `non_linear` for `/`, `companion` for `//`. VAST 4.2 places these under separate XML elements with separate event semantics (e.g., `acceptInvitation` is meaningful on non-linear / companion; `closeLinear` only on linear). Defaults to `linear`. Sales agents use this to place the tracker in the correct location during VAST assembly.' - ), - ] = Target31.linear - provenance: Annotated[ - Provenance550 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class EmbeddedProvenanceItem551(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1102 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark551(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1103 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction574(Jurisdiction229): - pass - - -class Disclosure553(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction574] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance551(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy551 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem551] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark551] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure553 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem551] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Assets28323(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['daast_tracker'], - Field( - description='Discriminator identifying this as a DAAST tracker asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'daast_tracker' - daast_event: Annotated[ - DAASTTrackingEvent, - Field( - description='The DAAST tracking event this URL fires on. MUST NOT be `impression` (model as `url` asset with `url_type: "tracker_pixel"`), `clickTracking` / `customClick` (click-tracking trackers go on their own URL asset), `error`, or any of the `ViewableImpression`-element children (`viewable`, `notViewable`, `viewUndetermined`, `measurableImpression`, `viewableImpression`).' - ), - ] - url: Annotated[ - str, - Field( - description='Tracker URL that fires when `daast_event` occurs. May carry AdCP universal macros; the sales agent or ad server URL-encodes substituted values at serve time. See docs/creative/universal-macros.mdx.' - ), - ] - offset: Annotated[ - str | None, - Field( - description='DAAST `offset` attribute. Required when `daast_event` is `progress` (DAAST 1.1 §3.2.4.3); ignored otherwise. Same format as VAST 4.2 `Tracking@offset`: `HH:MM:SS` or `HH:MM:SS.mmm` for absolute time (two-digit hours, minutes 00–59, seconds 00–59), or an integer percentage 0–100 suffixed with `%`. Negative offsets are NOT permitted.', - pattern='^(\\d{2}:[0-5]\\d:[0-5]\\d(\\.\\d{3})?|(100|\\d{1,2})%)$', - ), - ] = None - target: Annotated[ - Target32 | None, - Field( - description='Which DAAST creative element this tracker scopes to — `linear` for `/` (DAAST 1.1 §3.2.1.7), `companion` for `//` (DAAST 1.1 §3.2.2.7, where the only valid event is `creativeView`). DAAST has no `` element. Defaults to `linear`. Sales agents use this to place the tracker in the correct location during DAAST assembly.' - ), - ] = Target32.linear - provenance: Annotated[ - Provenance551 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance.', - title='Provenance', - ), - ] = None - - -class Assets2831( - RootModel[ - Assets2832 - | Assets2833 - | Assets2834 - | Assets2835 - | Assets2836 - | Assets2837 - | Assets2838 - | Assets2839 - | Assets28310 - | Assets28311 - | Assets28312 - | Assets28313 - | Assets28314 - | Assets28315 - | Assets160 - | Assets28317 - | Assets28318 - | Assets28319 - | Assets28320 - | Assets28321 - | Assets28322 - | Assets28323 - ] -): - root: Annotated[ - Assets2832 - | Assets2833 - | Assets2834 - | Assets2835 - | Assets2836 - | Assets2837 - | Assets2838 - | Assets2839 - | Assets28310 - | Assets28311 - | Assets28312 - | Assets28313 - | Assets28314 - | Assets28315 - | Assets160 - | Assets28317 - | Assets28318 - | Assets28319 - | Assets28320 - | Assets28321 - | Assets28322 - | Assets28323, - Field( - description='Canonical union of all asset variant schemas. Referenced from creative-asset.json and creative-manifest.json to ensure a single named type is emitted by schema-to-TypeScript tooling. Add new asset types here and to the creative/asset-types registry.', - discriminator='asset_type', - title='AssetVariant', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Assets283(RootModel[list[Assets2831]]): - root: Annotated[list[Assets2831], Field(min_length=1)] - - -class EmbeddedProvenanceItem552(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1104 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark552(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1105 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction575(Jurisdiction229): - pass - - -class Disclosure554(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction575] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance552(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy552 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem552] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark552] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure554 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem552] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance552 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride77(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo77 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand27(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride77 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Right12(Right7): - pass - - -class EmbeddedProvenanceItem553(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1106 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark553(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1107 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction576(Jurisdiction229): - pass - - -class Disclosure555(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction576] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance553(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy553 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem553] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark553] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure555 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem553] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class CreativeManifest8(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - format_id: Annotated[ - FormatId | None, - Field( - description="Legacy named-format path. Always a structured object {agent_url, id} — never a plain string. Format identifier this manifest is for. Can be a template format (id only) or a deterministic format (id + dimensions/duration). For dimension-specific creatives, include width/height in the format_id to create a unique identifier (e.g., {id: 'display_static', width: 300, height: 250}). Mutually exclusive with `format_kind`.", - title='Format Reference (Structured Object)', - ), - ] = None - format_kind: CanonicalFormatKind - format_option_ref: Annotated[ - FormatOptionRefs1 | FormatOptionRefs2 | None, - Field( - description='3.1+ format-option path, optional. Structured format option reference matching one of the target product\'s `format_options[]` declarations. Publisher-catalog-backed options match by `{ scope: "publisher", publisher_domain, format_option_id }`; product-local options match by `{ scope: "product", format_option_id }`. Required when the target product carries multiple `format_options` entries sharing the same `format_kind`; optional when `format_kind` alone routes the manifest to a single declaration. Product-scoped refs require an enclosing target product/package context.', - discriminator='scope', - title='Format Option Reference', - ), - ] = None - assets: Annotated[ - dict[ - Annotated[str, StringConstraints(pattern=r'^[a-z0-9_]+$')], - Assets261 - | Assets262 - | Assets263 - | Assets264 - | Assets265 - | Assets266 - | Assets267 - | Assets268 - | Assets269 - | Assets270 - | Assets271 - | Assets272 - | Assets273 - | Assets274 - | Assets275 - | Assets276 - | Assets277 - | Assets278 - | Assets279 - | Assets280 - | Assets281 - | Assets282 - | Assets283, - ], - Field( - description="Map of slot keys to actual asset content. Legacy named-format path: each key matches an `asset_id` from the format's `assets` array (e.g., 'banner_image', 'clickthrough_url', 'video_file', 'vast_tag'). 3.1+ canonical-format path: each key matches an `asset_group_id` from the format's `slots` declaration drawn from the canonical vocabulary registry (e.g., 'images_landscape', 'video', 'published_post', 'landing_page_url', 'vast_tag', 'script', 'creative_brief'). Either path produces the same envelope shape; only the slot-key vocabulary differs.\n\nEach slot value is **either** a single asset object (most slots — image, video, published_post, vast_tag, landing_page_url, etc.) **or** an array of asset objects (slots with `min`/`max` counts on the format declaration — `cards` on `image_carousel`, `headlines` / `descriptions` / `images_landscape` on `responsive_creative`, etc.). Single-vs-array shape is governed by the format's `slots[].min` and `slots[].max` parameters: when `max > 1` (or when the slot is conceptually a pool), the value MUST be an array; when the slot is single-valued, the value MUST be a single object. Each asset value (single or array element) carries an `asset_type` discriminator (image, video, audio, vast, daast, text, markdown, url, html, css, webhook, javascript, brief, catalog, published_post, zip, card) that selects the matching asset schema. Validators with OpenAPI-style discriminator support use `asset_type` to report errors against only the selected branch instead of all branches." - ), - ] - brand: Annotated[ - Brand27 | None, - Field( - description="Brand identity reference (BrandRef — `domain` plus optional `brand_id` for house-of-brands; plus optional inline `brand_kit_override` for per-creative tweaks where brand.json is missing/stale). When present, the seller pulls brand context (logos, colors, voice, taglines) from the brand's brand.json automatically; any `brand_kit_override` fields on the BrandRef take precedence. v2 formats no longer redeclare brand_logo / brand_colors / brand_voice as explicit slots — brand identity is implicit context.", - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - rights: Annotated[ - list[Right12] | None, - Field( - description='Rights constraints attached to this creative. Each entry represents constraints from a single rights holder. A creative may combine multiple rights constraints (e.g., talent likeness + music license). For v1, rights constraints are informational metadata — the buyer/orchestrator manages creative lifecycle against these terms.' - ), - ] = None - industry_identifiers: Annotated[ - list[IndustryIdentifier] | None, - Field( - description='Industry-standard or market-specific identifiers for this specific manifest (e.g., Ad-ID, ISCI, Clearcast clock number, IDcrea). When present, overrides creative-level identifiers. Use when different format versions of the same source creative have distinct traffic identifiers (e.g., the :15 and :30 cuts, or separate TV and radio versions). Add a PR to extend creative-identifier-type when another shared identifier scheme needs first-class support.' - ), - ] = None - provenance: Annotated[ - Provenance553 | None, - Field( - description='Provenance metadata for this creative manifest. Serves as the default provenance for all assets in this manifest. An asset with its own provenance replaces this object entirely (no field-level merging).', - title='Provenance', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Variant(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - build_variant_id: Annotated[ - str, - Field( - description='Build-time handle for this produced variant — the leaf-level lineage anchor for the creative. Minted per produced variant. Its OWN namespace — MUST NOT reuse a `preview_id` (preview renders) or a served `variant_id` (delivery). Refinement parentage and build-time QA re-rolls anchor on this leaf id, NOT on the call-level `build_creative_id`. On the canonical promotion path, this value becomes the durable `creative_id` when the leaf is trafficked / added to the library; delivery outcomes and `report_usage` then join by `creative_id`. An untrafficked leaf has no `report_usage` key — it is billed via the inline per-leaf `vendor_cost` only, and `report_usage` reconciliation applies once the leaf earns a `creative_id`.' - ), - ] - recipe_hash: Annotated[ - str | None, - Field( - description='Optional agent-computed, opaque, agent-scoped identity for the build-determining inputs that produced this variant leaf. Stable for identical inputs as defined by the agent, and comparable only within the same agent. Multiple leaves from the same best-of-N recipe SHOULD carry the same value so clients can group alternative outputs by their shared source recipe. ETag-style semantics: the protocol defines the field and contract, not the hash algorithm or canonical input set. Identifies generative-input identity, not output equality, legal/disclosure equivalence, or the build-to-delivery join.' - ), - ] = None - parent_build_variant_id: Annotated[ - str | None, - Field( - description="When this variant was produced by refining a prior build (request `refine_from_build_variant_id`), the source leaf's `build_variant_id` — establishing refinement lineage (a leaf may itself be refined, forming a chain). Absent for first-generation builds. AI-derivative attribution rides the manifest's existing `provenance`; this field carries only the lineage edge." - ), - ] = None - creative_manifest: Annotated[ - CreativeManifest7 | CreativeManifest8, - Field( - description='The generated creative for this variant. Carries its own format_id.', - title='Creative Manifest', - ), - ] - variant_axis_value: Annotated[ - Any | None, - Field( - description='The value of the variant axis that produced this variant (e.g. the voice id, theme name, or config value). Lets the buyer correlate the variant to its A/B cell.' - ), - ] = None - recommended: Annotated[ - bool | None, - Field( - description="Set when keep_mode was keep_one/keep_some — flags the agent's recommended pick(s). Advisory." - ), - ] = None - rank: Annotated[ - int | None, - Field( - description="Agent's ranking of this variant (1 = best) when it scored alternatives (best-of-N). Advisory.", - ge=1, - ), - ] = None - eval: Annotated[ - Eval | None, - Field( - description="Optional per-leaf evaluation block populated when the request supplied an `evaluator` and the agent advertises creative.supports_evaluator. Experimental (x-status: experimental) — part of the evaluator surface; sellers populating it MUST list `creative.evaluator` in experimental_features. The rank-side of the get_creative_features feature oracle: it carries the creative-feature values this leaf was scored on, which is what the gate-then-rank pipeline (evaluator.feature_requirement[] gate → evaluator.rank_by ordering) and `recommended`/`rank` are computed over. `eval.features[]` is `creative/creative-feature-result.json[]` — the same shape get_creative_features.results[] returns; the wrapper is open (additionalProperties:true) while each feature item is closed. A leaf's eval is a feature measurement, not a pass/fail verdict (a verdict is a categorical string feature value gated via feature_requirement.allowed_values). Leaves the agent dropped via the gate are not present; this block appears only on returned (recommended/billed) leaves. Advisory; does not change what is produced or billed." - ), - ] = None - pricing_option_id: Annotated[ - str | None, - Field( - description='Which rate-card pricing option was applied for THIS variant leaf. Pass in report_usage after promotion.' - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Cost incurred for this variant leaf, denominated in currency. REQUIRED on every produced leaf whenever the build reports cost (the top-level aggregate `vendor_cost` is present) — leaves are the billing source of truth, and an untrafficked leaf is reconciled from this field alone (it never earns a `creative_id` / `report_usage` entry). A CPM-deferred leaf reports 0 here (a value, not an omission).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for vendor_cost. Co-required with vendor_cost.', - pattern='^[A-Z]{3}$', - ), - ] = None - consumption: Annotated[ - Consumption | None, - Field( - description='Structured consumption for this variant leaf. Informational; vendor_cost is the billing source of truth.', - title='Creative Consumption', - ), - ] = None - - -class Creative(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - build_creative_id: Annotated[ - str | None, - Field( - description='Build-time handle for this produced creative within this response. Distinct from a library creative_id — a build_creative_id is not yet persisted/servable; it acquires a creative_id when a chosen variant is trafficked or added to the library (lazy promotion).' - ), - ] = None - catalog_item_ref: Annotated[ - CatalogItemRef | None, - Field( - description='When this creative was produced by fanning out over a catalog, identifies the source item.' - ), - ] = None - signal_condition: Annotated[ - SignalCondition | SignalCondition9 | SignalCondition10 | None, - Field( - description='When this creative group was produced by fanning out over signal_conditions, the SignalTargeting condition this group is FOR (e.g. weather=rain). Sibling to catalog_item_ref. Carries the SAME signal_ref identity the sales-side package targeting uses, so a sales agent can structurally match condition identity and reject-at-trafficking (SIGNAL_TARGETING_INCOMPATIBLE) when a creative is assigned to an incompatible package.', - discriminator='value_type', - title='Signal Targeting', - ), - ] = None - variants: Annotated[ - list[Variant] | None, - Field( - description='Choose-among alternatives produced for this creative group (voices, themes, best-of-N, etc.). At least one. Each is an independently-tagged, independently-billed build.', - min_length=1, - ), - ] = None - errors: Annotated[ - list[Error18] | None, - Field( - description='Per-creative errors when this catalog item failed to build. Present only on failed items; does not fail the batch (per-item non-atomic). A failed entry carries errors[] and no variants[]; a successful entry carries variants[] and SHOULD NOT carry errors.', - min_length=1, - ), - ] = None - - -class Result484(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError23 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig25 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - creatives: Annotated[ - list[Creative], - Field( - description='One entry per produced creative group. With catalog fan-out, one entry per catalog item (bounded/sampled by max_creatives). This array is the produced group set; use variants[] inside each group for choose-among alternatives.', - min_length=1, - ), - ] - items_total: Annotated[ - int | None, - Field( - description='Total catalog items eligible for the build (before max_creatives sampling). Lets the buyer see that creatives[] is a sample of a larger set.', - ge=0, - ), - ] = None - items_returned: Annotated[ - int | None, - Field( - description='Number of creatives returned in creatives[] (after max_creatives sampling).', - ge=0, - ), - ] = None - leaves_total: Annotated[ - int | None, - Field( - description='Total leaves the request would have produced (≈ items_to_produce × variants_per_item, × conditions_total when signal_conditions was sent). Present when a max_spend cap may have stopped production short. Counts LEAVES, not catalog items — so it expresses a shortfall even for a variant-only fan-out with no catalog.', - ge=0, - ), - ] = None - leaves_returned: Annotated[ - int | None, - Field( - description="Number of leaves actually produced and billed across creatives[].variants[]. When budget_status is 'capped', leaves_returned < leaves_total is the leaf-granular shortfall signal (items_returned/items_total are catalog-item counts and do not capture a mid-item or variant-only cap).", - ge=0, - ), - ] = None - vendor_cost: Annotated[ - float | None, - Field( - description='Aggregate cost across all variant leaves, denominated in currency. MUST equal the sum of the per-leaf vendor_cost values (leaves are the source of truth). When present, every produced leaf MUST carry its own vendor_cost + currency (enforced) so the sum invariant is checkable; omit this aggregate only for a genuinely free build.', - ge=0.0, - ), - ] = None - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code for the aggregate vendor_cost.', - pattern='^[A-Z]{3}$', - ), - ] = None - keep_mode_applied: Annotated[ - KeepModeApplied | None, - Field( - description="Echoes the `keep_mode` the agent applied (mirrors the request hint). Present when the request set `keep_mode`. `keep_mode` is advisory — it does not change what is produced or billed (you pay for every leaf in `variants[]`) — so this echo is the buyer's confirmation that the hint was received, the audit paper trail for a 'I asked for keep_one but was billed for N' dispute. Whether the agent acted on it shows in the `recommended`/`rank` it set on the leaves." - ), - ] = None - selection_strategy_applied: Annotated[ - SelectionStrategyApplied | None, - Field( - description='Echoes the selection_strategy the agent applied when max_creatives sampling occurred (mirrors keep_mode_applied). Present when the request set selection_strategy. The ranking itself shows in the rank/recommended on creatives[].variants[].', - title='Creative Selection Strategy', - ), - ] = None - budget_status: Annotated[ - BudgetStatus | None, - Field( - description='`complete` (default; absent == complete for back-compat) means the agent produced everything requested. `capped` means a `max_spend` ceiling stopped production early: every returned leaf is real/billed, and an advisory `BUDGET_CAP_REACHED` entry in `errors[]` is the authoritative cap signal. The leaf-granular shortfall is `leaves_returned` < `leaves_total` (do NOT rely on items_returned < items_total — that is also the normal max_creatives-sampling signal and does not capture a mid-item or variant-only cap). This is a successful partial build, not a failure.' - ), - ] = BudgetStatus.complete - errors: Annotated[ - list[Error19] | None, - Field( - description='Advisory (non-terminal) entries on an otherwise-successful build — e.g. a `BUDGET_CAP_REACHED` notice when `budget_status` is `capped`. Terminal failures use the BuildCreativeError shape, not this field.' - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the earliest generated asset URL expires across all variants. Re-build after this time to get fresh URLs.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig26(PushNotificationConfig18): - pass - - -class Result577(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError24 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig26 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - mode: Annotated[ - Literal['estimate'], - Field( - description='Echoes the request mode; discriminates this dry-run envelope from the producing shapes.' - ), - ] = 'estimate' - estimate: Annotated[ - Estimate, Field(description='Projected cost for what mode:"execute" would have produced.') - ] - expires_at: Annotated[ - AwareDatetime | None, - Field( - description="ISO 8601 timestamp after which this estimate's inputs/prices may no longer hold." - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig27(PushNotificationConfig18): - pass - - -class Result578(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError25 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig27 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error20], - Field( - description='Array of errors explaining why creative generation failed.\n\n**Per-format attribution on multi-format requests.** When the request used `target_format_ids[]` (a batch request), sellers MUST attribute each failure to the specific format(s) that caused it so buyers can correct and retry the failing subset rather than re-running the entire batch. Two attribution surfaces, populated together:\n\n1. **`error.field`** — set to `target_format_ids[N]` where `N` is the zero-based index of the failing format in the request\'s `target_format_ids[]` array (e.g., `target_format_ids[1]` for the second requested format). Mirrors the JSONPath-lite convention `error.field` uses elsewhere. Required when the error is format-scoped.\n2. **`error.details.format_id`** — the resolved `format_id` value (e.g., `"meta-reels-9x16"`). Required when the error is format-scoped. Lets buyers dispatch on the format identity without re-parsing `error.field`.\n\nErrors not attributable to a specific format (whole-batch failures: authentication, governance denial, transport-level errors) MAY omit `field` and `details.format_id`. Buyers MUST treat per-format errors as scoped to the named format only — a `correctable` error on `target_format_ids[1]` does NOT mean the buyer must reshape the entire batch; they may retry just that format with corrected input. Sellers SHOULD emit one error per failing format (rather than collapsing multiple format failures into a single error entry) so per-format recovery routing is unambiguous.', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig28(PushNotificationConfig18): - pass - - -class Result579(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError26 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig28 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error21] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig29(PushNotificationConfig18): - pass - - -class EmbeddedProvenanceItem554(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1108 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark554(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1109 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction577(Jurisdiction229): - pass - - -class Disclosure556(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction577] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance554(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy554 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem554] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark554] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure556 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem554] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance554 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride78(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo78 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand28(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride78 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class ReportingBucket2(ReportingBucket): - pass - - -class Authentication31(Authentication18): - pass - - -class NotificationConfig2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - subscriber_id: Annotated[ - str, - Field( - description="Buyer-supplied identifier for this subscription endpoint. This is the stable logical key within one account's notification_configs[] set: re-sending the same subscriber_id for the same account replaces that subscriber's URL, event_types, authentication selector, and active flag rather than creating a duplicate. Echoed on every webhook payload and on every `webhook_activity[]` record fired against this config so the buyer can attribute fires across multiple endpoints. MUST be unique within the account's `notification_configs[]`. Sending two entries with the same `subscriber_id` in a single `sync_accounts` request array is rejected as a per-account validation failure with `INVALID_REQUEST` or `VALIDATION_ERROR`, and `error.field` MUST point at the duplicate entry. `subscriber_id` is the stable match key for the per-account declarative-replace diff. Always required (even with a single subscriber) so the SDK contract is uniform — no conditional required-when-multiple rules to trip up implementations. Format is opaque — recommended values are short kebab-case slugs (`buyer-primary`, `audit-bus`, `dx-team`).", - max_length=64, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,64}$', - ), - ] - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL. Same wire contract as `push-notification-config.url` — `format: "uri"`, no destination-port allowlist enforced by the protocol, SSRF protection via the IP-range check defined in docs/building/by-layer/L1/security.mdx#webhook-url-validation-ssrf. Sellers MUST validate URL syntax, HTTPS usage, hostname normalization, and reserved-range rejection when writing any config, including `active: false` configs. Sellers MUST complete an activation challenge or equivalent proof-of-control before treating a new or changed active subscriber as active.' - ), - ] - event_types: Annotated[ - list[NotificationType], - Field( - description='Notification types this subscriber wishes to receive on the registered `url`. The seller MUST NOT fire other types against this endpoint, and MUST NOT silently widen the filter when new types are added to `notification-type.json`. When omitted, the seller MUST default to a no-fire policy and surface an `errors[]` entry on `sync_accounts` so the buyer notices the missing filter. Values are drawn from `notification-type.json`, but only types whose contract anchors at the account scope are valid here — creative lifecycle events and wholesale feed change payloads are valid; media-buy-anchored types (`scheduled`, `final`, `delayed`, `adjusted`, `impairment`) and account-lifecycle names not present in the enum (for example, `account.status_changed`) are invalid on this surface; sellers MUST reject those entries as per-account validation failures with `INVALID_REQUEST` or `VALIDATION_ERROR` and `error.field` pointing at the invalid `event_types` entry rather than silently dropping them.', - min_length=1, - ), - ] - authentication: Annotated[ - Authentication31 | None, - Field( - description="Legacy authentication selector. Same precedence and semantics as `push-notification-config.authentication` — presence opts the seller into Bearer or HMAC-SHA256 signing; absence selects the default RFC 9421 webhook profile keyed off the seller's brand.json `agents[]` JWKS. The same signed-registration downgrade-resistance rules apply to accounts[].notification_configs[].authentication. Deprecated; removed in AdCP 4.0. Credentials are write-only and MUST NOT be echoed on `list_accounts` reads." - ), - ] = None - active: Annotated[ - bool | None, - Field( - description="When false, the seller persists the configuration but suppresses fires. Use to pause a noisy subscriber without losing the registration. Sellers MUST NOT skip persisting the entry when `active: false` — the buyer's next `sync_accounts` MUST observe the same array, otherwise the buyer cannot distinguish pause from drop. Paused configs may skip only the outbound proof challenge while inactive; sellers MUST still enforce URL parsing, HTTPS, hostname normalization, and reserved-range rejection at write time. Reactivation requires full SSRF validation with connect pinning plus proof-of-control for any tuple without current valid proof." - ), - ] = True - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Account27(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - account_id: Annotated[str, Field(description='Unique identifier for this account')] - name: Annotated[ - str, Field(description="Human-readable account name (e.g., 'Acme', 'Acme c/o Pinnacle')") - ] - advertiser: Annotated[ - str | None, Field(description='The advertiser whose rates apply to this account') - ] = None - billing_proxy: Annotated[ - str | None, - Field( - description='Optional intermediary who receives invoices on behalf of the advertiser (e.g., agency)' - ), - ] = None - status: AccountStatus - brand: Annotated[ - Brand28 | None, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - operator: Annotated[ - str | None, - Field( - description="Domain of the entity operating this account. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - billing: BillingParty | None = None - billing_entity: Annotated[ - BillingEntity1 | None, - Field( - description='Business entity details for the party responsible for payment. Contains the legal name, tax IDs, address, and bank details needed for formal B2B invoicing. Corresponds to whoever billing points to (operator, agent, or advertiser). When this account appears in a response, bank details MUST be omitted (write-only).', - examples=[ - { - 'description': 'German agency with full B2B details', - 'data': { - 'legal_name': 'Pinnacle Media GmbH', - 'vat_id': 'DE123456789', - 'registration_number': 'HRB 12345', - 'address': { - 'street': 'Friedrichstrasse 100', - 'city': 'Berlin', - 'postal_code': '10117', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'Sam Adeyemi', - 'email': 'billing@pinnacle-media.com', - 'phone': '+49 30 12345678', - } - ], - 'bank': { - 'account_holder': 'Pinnacle Media GmbH', - 'iban': 'DE89370400440532013000', - 'bic': 'COBADEFFXXX', - }, - }, - }, - { - 'description': 'US advertiser with EIN and domestic bank details', - 'data': { - 'legal_name': 'Acme Corporation', - 'tax_id': '12-3456789', - 'address': { - 'street': '123 Main St', - 'city': 'New York', - 'postal_code': '10001', - 'region': 'NY', - 'country': 'US', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'ap@acme-corp.com', - } - ], - 'bank': { - 'account_holder': 'Acme Corporation', - 'routing_number': '021000021', - 'account_number': '123456789', - }, - }, - }, - ], - title='Business Entity', - ), - ] = None - rate_card: Annotated[ - str | None, Field(description='Identifier for the rate card applied to this account') - ] = None - payment_terms: PaymentTerms | None = None - credit_limit: Annotated[ - CreditLimit | None, Field(description='Maximum outstanding balance allowed') - ] = None - setup: Annotated[ - Setup | None, - Field( - description="Present when status is 'pending_approval'. Contains next steps for completing account activation." - ), - ] = None - account_scope: AccountScope | None = None - governance_agents: Annotated[ - list[GovernanceAgent] | None, - Field( - description="Governance agent endpoint registered on this account. Exactly one entry per sync_governance's one-agent-per-account invariant. The array shape is preserved for wire compatibility with 3.0; `maxItems: 1` is load-bearing and mirrors the singular `governance_context` on the protocol envelope. Authentication credentials are write-only and not included in responses — use sync_governance to set or update credentials.", - max_length=1, - min_length=1, - ), - ] = None - reporting_bucket: Annotated[ - ReportingBucket2 | None, - Field( - description="Cloud storage bucket where the seller delivers offline reporting files for this account. Seller provisions a dedicated bucket or a per-account prefix within a shared bucket, and grants the buyer read access out-of-band. Access MUST be scoped at the IAM layer so each account can only read its own prefix — bucket-wide grants are non-compliant even with per-account prefixes. Seller MUST revoke access when the account's status transitions to inactive, suspended, or closed. See security considerations for offline delivery in docs/media-buy/media-buys/optimization-reporting. Only present when the seller supports offline delivery (reporting_delivery_methods includes 'offline' in capabilities)." - ), - ] = None - sandbox: Annotated[ - bool | None, - Field( - description='When true, this is a sandbox account — no real platform calls, no real spend. For account-id namespaces, sandbox accounts are pre-existing test accounts on the platform discovered via list_accounts or supplied out-of-band. For buyer-declared accounts, sandbox is part of the natural key: the same brand/operator pair can have both a production and sandbox account.' - ), - ] = None - notification_configs: Annotated[ - list[NotificationConfig2] | None, - Field( - description="Account-level webhook subscriptions for notifications whose lifecycle outlives any single media buy (e.g., `creative.status_changed`, `creative.purged`, wholesale feed change payloads). This is an account-scoped delivery surface, not an account-object lifecycle event stream; account status changes are observed through `list_accounts` polling or the one-shot `sync_accounts.push_notification_config` async result channel. Distinct from `push_notification_config` on individual operations, which anchors at a per-resource scope. Buyers register and update entries via `sync_accounts`; sellers echo the applied state here on `list_accounts` reads so buyers can verify what's active. The set is keyed by account-scoped `subscriber_id`; re-registering the same `subscriber_id` replaces that subscriber's config. `authentication.credentials` is write-only — sellers MUST NOT echo legacy auth credentials in this response. When two or more entries register the same `event_types`, each receives an independent fire — see #3009 multi-subscriber composition.", - max_length=16, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Creative5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - creative_id: Annotated[str, Field(description='Creative ID from the request')] - account: Annotated[ - Account27 | None, - Field( - description='Account that owns this creative', - examples=[ - { - 'description': 'Direct advertiser account', - 'data': { - 'account_id': 'acc_acme_direct', - 'name': 'Acme', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Advertiser account with agency billing proxy', - 'data': { - 'account_id': 'acc_acme_pinnacle', - 'name': 'Acme c/o Pinnacle', - 'advertiser': 'Acme Corp', - 'billing_proxy': 'Pinnacle Media', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator_brand', - 'rate_card': 'acme_vip_2024', - 'payment_terms': 'net_60', - }, - }, - { - 'description': 'Agency as direct buyer', - 'data': { - 'account_id': 'acc_pinnacle', - 'name': 'Pinnacle', - 'advertiser': 'Pinnacle Media', - 'brand': {'domain': 'pinnacle-media.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'operator', - 'account_scope': 'operator', - 'rate_card': 'agency_standard', - 'payment_terms': 'net_45', - }, - }, - { - 'description': 'Account with brand identity and operator (via sync_accounts)', - 'data': { - 'account_id': 'acc_spark_001', - 'name': 'Spark (via Pinnacle)', - 'advertiser': 'Nova Brands', - 'status': 'active', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - 'billing': 'agent', - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - { - 'description': 'Pending account awaiting seller approval', - 'data': { - 'account_id': 'acc_glow_pending', - 'name': 'Glow', - 'advertiser': 'Nova Brands', - 'status': 'pending_approval', - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - 'operator': 'pinnacle-media.com', - 'billing': 'operator', - 'account_scope': 'brand', - }, - }, - { - 'description': 'Agency operates but advertiser is billed directly with structured billing entity', - 'data': { - 'account_id': 'acc_acme_direct_bill', - 'name': 'Acme (billed direct)', - 'advertiser': 'Acme Corp', - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'pinnacle-media.com', - 'status': 'active', - 'billing': 'advertiser', - 'billing_entity': { - 'legal_name': 'Acme Corporation GmbH', - 'vat_id': 'DE987654321', - 'address': { - 'street': 'Hauptstrasse 42', - 'city': 'Munich', - 'postal_code': '80331', - 'country': 'DE', - }, - 'contacts': [ - { - 'role': 'billing', - 'name': 'AP Department', - 'email': 'billing@acme-corp.com', - } - ], - }, - 'account_scope': 'operator_brand', - 'payment_terms': 'net_30', - }, - }, - ], - title='Account', - ), - ] = None - action: Annotated[ - Action, - Field( - description='Action taken for this creative during this sync operation (lifecycle operation, not approval state).', - title='Creative Action', - ), - ] - status: Annotated[ - Status154 | None, - Field( - description='Advisory review-lifecycle state of the creative after this sync — a UI hint and polling-scheduling signal, NOT a spend-authorization gate. Orthogonal to action — action says what the sync did (created, updated, ...); status says where the creative sits in review. Values come from CreativeStatus only (processing, pending_review, approved, suspended, rejected, archived) — never from CreativeAction. Sellers with async review return processing or pending_review; sellers with synchronous review MAY return a terminal value (approved, rejected) or suspended when a recoverable dependency/authorization gate prevents serving. Buyers MUST NOT gate downstream spend or package activation on status: approved from this response — a compromised or buggy seller could declare approved while bypassing content-policy review. Reconcile via list_creatives or a signed review webhook before committing spend. Authoritative state is always via list_creatives. MUST be omitted when action is failed or deleted (the creative has no meaningful review state — failure details belong in the errors array; deleted creatives are gone from the library). Omit entirely when the seller has no review lifecycle at all.', - title='Creative Status', - ), - ] = None - platform_id: Annotated[ - str | None, Field(description='Platform-specific ID assigned to the creative') - ] = None - changes: Annotated[ - list[str] | None, - Field(description="Field names that were modified (only present when action='updated')"), - ] = None - errors: Annotated[ - list[Error24] | None, - Field(description="Validation or processing errors (only present when action='failed')"), - ] = None - warnings: Annotated[ - list[str] | None, Field(description='Non-fatal warnings about this creative') - ] = None - preview_url: Annotated[ - AnyUrl | None, - Field( - description='Preview URL for generative creatives (only present for generative formats)' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when preview link expires (only present when preview_url exists)' - ), - ] = None - assigned_to: Annotated[ - list[str] | None, - Field( - description='Package IDs this creative was successfully assigned to (only present when assignments were requested)' - ), - ] = None - assignment_errors: Annotated[ - dict[Annotated[str, StringConstraints(pattern=r'^[a-zA-Z0-9_-]+$')], str] | None, - Field( - description='Assignment errors by package ID (only present when assignment failures occurred)' - ), - ] = None - - -class Result583(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError27 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig29 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - dry_run: Annotated[ - bool | None, Field(description='Whether this was a dry run (no actual changes made)') - ] = None - creatives: Annotated[ - list[Creative5], - Field( - description="Results for each creative processed. Items with action='failed' indicate per-item validation/processing failures, not operation-level failures." - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Authentication32(Authentication): - pass - - -class PushNotificationConfig30(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication32 | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Result585(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError28 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig30 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error25], - Field( - description='Operation-level errors that prevented processing any creatives (e.g., authentication failure, service unavailable, invalid request format)', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig31(PushNotificationConfig30): - pass - - -class Result586(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError29 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig31 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error26] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig32(PushNotificationConfig30): - pass - - -class Result590(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError30 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig32 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - dry_run: Annotated[ - bool | None, Field(description='Whether this was a dry run (no actual changes made)') - ] = None - catalogs: Annotated[ - list[Catalog], - Field( - description="Results for each catalog processed. Items with action='failed' indicate per-catalog validation/processing failures, not operation-level failures." - ), - ] - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig33(PushNotificationConfig30): - pass - - -class Result591(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError31 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig33 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error29], - Field( - description='Operation-level errors that prevented processing any catalogs (e.g., authentication failure, service unavailable, invalid request format)', - min_length=1, - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PushNotificationConfig34(PushNotificationConfig30): - pass - - -class Result592(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError32 | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig34 | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - errors: Annotated[ - list[Error30] | None, - Field( - description='Optional advisory errors accompanying the submitted envelope. Use only for non-blocking warnings (e.g., throttled_severity advisories, governance observations). Terminal failures belong in the error branch, not here.' - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class GetTaskStatusResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - task_type: Annotated[TaskType, Field(description='Type of AdCP operation', title='Task Type')] - protocol: AdCPProtocol - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Whether this task has webhook configuration') - ] = None - progress: Annotated[ - Progress | None, Field(description='Progress information for long-running tasks') - ] = None - error: Annotated[Error5 | None, Field(description='Error details for failed tasks')] = None - history: Annotated[ - list[HistoryItem] | None, - Field( - description='Complete conversation history for this task (only included if include_history was true in request)' - ), - ] = None - result: Annotated[ - Result219 - | Result244 - | Result245 - | Result266 - | Result267 - | Result270 - | Result271 - | Result272 - | Result278 - | Result279 - | Result280 - | Result281 - | Result282 - | Result283 - | Result288 - | Result289 - | Result290 - | Result291 - | Result292 - | Result293 - | Result298 - | Result391 - | Result484 - | Result577 - | Result578 - | Result579 - | Result580 - | Result581 - | Result582 - | Result583 - | Result585 - | Result586 - | Result587 - | Result588 - | Result589 - | Result590 - | Result591 - | Result592 - | Result593 - | Result594 - | Result595 - | None, - Field( - description="Task-specific completion payload. Present when status is 'completed' and include_result was true in the request; absent otherwise. For failed tasks, use the error field instead. Uses the same anyOf union as the push-notification webhook result field.", - title='AdCP Async Response Data', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/protocol/list_tasks_request.py b/src/adcp/types/generated_poc/bundled/protocol/list_tasks_request.py deleted file mode 100644 index 52a2410e..00000000 --- a/src/adcp/types/generated_poc/bundled/protocol/list_tasks_request.py +++ /dev/null @@ -1,765 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/protocol/list_tasks_request.json -# timestamp: 2026-06-07T22:46:16+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1213(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1213 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account38(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class Field1(StrEnum): - created_at = 'created_at' - updated_at = 'updated_at' - status = 'status' - task_type = 'task_type' - protocol = 'protocol' - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class Sort(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: Annotated[Field1 | None, Field(description='Field to sort by')] = Field1.created_at - direction: Annotated[ - Direction | None, Field(description='Sort direction', title='Sort Direction') - ] = Direction.desc - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class AdCPProtocol(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - governance = 'governance' - creative = 'creative' - brand = 'brand' - sponsored_intelligence = 'sponsored-intelligence' - measurement = 'measurement' - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - protocol: AdCPProtocol | None = None - protocols: Annotated[ - list[AdCPProtocol] | None, - Field(description='Filter by multiple AdCP protocols', min_length=1), - ] = None - status: TaskStatus | None = None - statuses: Annotated[ - list[TaskStatus] | None, Field(description='Filter by multiple task statuses', min_length=1) - ] = None - task_type: TaskType | None = None - task_types: Annotated[ - list[TaskType] | None, Field(description='Filter by multiple task types', min_length=1) - ] = None - created_after: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created after this date (ISO 8601)') - ] = None - created_before: Annotated[ - AwareDatetime | None, Field(description='Filter tasks created before this date (ISO 8601)') - ] = None - updated_after: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated after this date (ISO 8601)'), - ] = None - updated_before: Annotated[ - AwareDatetime | None, - Field(description='Filter tasks last updated before this date (ISO 8601)'), - ] = None - task_ids: Annotated[ - list[str] | None, - Field(description='Filter by specific task IDs', max_length=100, min_length=1), - ] = None - context_contains: Annotated[ - str | None, - Field( - description='Filter tasks where context contains this text (searches media_buy_id, signal_id, etc.)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Filter tasks that have webhook configuration when true') - ] = None - - -class ListTasksRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - account: Annotated[ - Account | Account38 | None, - Field( - description="Account scope for task reconciliation. Sellers MUST only return tasks created for the caller's authenticated account + principal pair. When omitted, the seller MAY use the credential-bound singleton account, but multi-account credentials SHOULD require an explicit account.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - filters: Annotated[Filters | None, Field(description='Filter criteria for querying tasks')] = ( - None - ) - sort: Annotated[Sort | None, Field(description='Sorting parameters')] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination parameters for list operations', - title='Pagination Request', - ), - ] = None - include_history: Annotated[ - bool | None, - Field( - description='Include full conversation history for each task (may significantly increase response size)' - ), - ] = False - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/protocol/list_tasks_response.py b/src/adcp/types/generated_poc/bundled/protocol/list_tasks_response.py deleted file mode 100644 index 73a80ac0..00000000 --- a/src/adcp/types/generated_poc/bundled/protocol/list_tasks_response.py +++ /dev/null @@ -1,451 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/protocol/list_tasks_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class DomainBreakdown(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_buy: Annotated[ - int | None, - Field(alias='media-buy', description='Number of media-buy tasks in results', ge=0), - ] = None - signals: Annotated[ - int | None, Field(description='Number of signals tasks in results', ge=0) - ] = None - creative: Annotated[ - int | None, Field(description='Number of creative tasks in results', ge=0) - ] = None - - -class Direction(StrEnum): - asc = 'asc' - desc = 'desc' - - -class SortApplied(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - field: str - direction: Direction - - -class QuerySummary(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - total_matching: Annotated[ - int | None, - Field(description='Total number of tasks matching filters (across all pages)', ge=0), - ] = None - returned: Annotated[ - int | None, Field(description='Number of tasks returned in this response', ge=0) - ] = None - domain_breakdown: Annotated[ - DomainBreakdown | None, Field(description='Count of tasks by domain') - ] = None - status_breakdown: Annotated[ - dict[str, int] | None, Field(description='Count of tasks by status') - ] = None - filters_applied: Annotated[ - list[str] | None, Field(description='List of filters that were applied to the query') - ] = None - sort_applied: Annotated[ - SortApplied | None, Field(description='Sort order that was applied') - ] = None - - -class TaskType(StrEnum): - create_media_buy = 'create_media_buy' - update_media_buy = 'update_media_buy' - media_buy_delivery = 'media_buy_delivery' - sync_creatives = 'sync_creatives' - build_creative = 'build_creative' - activate_signal = 'activate_signal' - get_products = 'get_products' - get_signals = 'get_signals' - create_property_list = 'create_property_list' - update_property_list = 'update_property_list' - get_property_list = 'get_property_list' - list_property_lists = 'list_property_lists' - delete_property_list = 'delete_property_list' - sync_accounts = 'sync_accounts' - get_account_financials = 'get_account_financials' - get_creative_delivery = 'get_creative_delivery' - sync_event_sources = 'sync_event_sources' - sync_audiences = 'sync_audiences' - sync_catalogs = 'sync_catalogs' - log_event = 'log_event' - get_brand_identity = 'get_brand_identity' - search_brands = 'search_brands' - get_rights = 'get_rights' - acquire_rights = 'acquire_rights' - - -class Domain(StrEnum): - media_buy = 'media-buy' - signals = 'signals' - creative = 'creative' - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class TaskStatus(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class Task(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - task_id: Annotated[str, Field(description='Unique identifier for this task')] - task_type: Annotated[TaskType, Field(description='Type of AdCP operation', title='Task Type')] - domain: Annotated[Domain, Field(description='AdCP domain this task belongs to')] - status: TaskStatus - created_at: Annotated[ - AwareDatetime, Field(description='When the task was initially created (ISO 8601)') - ] - updated_at: Annotated[ - AwareDatetime, Field(description='When the task was last updated (ISO 8601)') - ] - completed_at: Annotated[ - AwareDatetime | None, - Field( - description='When the task completed (ISO 8601, only for completed/failed/canceled tasks)' - ), - ] = None - has_webhook: Annotated[ - bool | None, Field(description='Whether this task has webhook configuration') - ] = None - - -class ListTasksResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: TaskStatus - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - query_summary: Annotated[ - QuerySummary, Field(description='Summary of the query that was executed') - ] - tasks: Annotated[list[Task], Field(description='Array of tasks matching the query criteria')] - pagination: Annotated[ - Pagination, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/signals/__init__.py b/src/adcp/types/generated_poc/bundled/signals/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/signals/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/signals/activate_signal_request.py b/src/adcp/types/generated_poc/bundled/signals/activate_signal_request.py deleted file mode 100644 index 5775db1d..00000000 --- a/src/adcp/types/generated_poc/bundled/signals/activate_signal_request.py +++ /dev/null @@ -1,704 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/signals/activate_signal_request.json -# timestamp: 2026-05-28T10:34:10+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Action(StrEnum): - activate = 'activate' - deactivate = 'deactivate' - - -class Destinations1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[ - str, - Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"), - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the platform') - ] = None - - -class Destinations2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the agent') - ] = None - - -class Destinations(RootModel[Destinations1 | Destinations2]): - root: Annotated[ - Destinations1 | Destinations2, - Field( - description='A deployment target where signals can be activated (DSP, sales agent, etc.)', - discriminator='type', - title='Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class ActivateSignalRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - action: Annotated[ - Action | None, - Field( - description="Whether to activate or deactivate the signal. Deactivating removes the segment from downstream platforms, required when campaigns end to comply with data governance policies (GDPR, CCPA). Defaults to 'activate' when omitted." - ), - ] = Action.activate - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque activation handle returned in the signal_agent_segment_id field of each get_signals response entry. Pass this string verbatim — do not pass the signal_id object.' - ), - ] - destinations: Annotated[ - list[Destinations], - Field( - description='Target destination(s) for activation. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', - min_length=1, - ), - ] - pricing_option_id: Annotated[ - str | None, - Field( - description="The pricing option selected from the signal's pricing_options in the get_signals response. Required when the signal has pricing options. Records the buyer's pricing commitment at activation time; pass this same value in report_usage for billing verification." - ), - ] = None - account: Annotated[ - Account | Account2 | None, - Field( - description='Account for this activation. Associates with a commercial relationship established via sync_accounts.', - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate activations on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/signals/activate_signal_response.py b/src/adcp/types/generated_poc/bundled/signals/activate_signal_response.py deleted file mode 100644 index 3f71b284..00000000 --- a/src/adcp/types/generated_poc/bundled/signals/activate_signal_response.py +++ /dev/null @@ -1,302 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/signals/activate_signal_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class ActivateSignalResponse(AdCPBaseModel): - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/signals/get_signals_request.py b/src/adcp/types/generated_poc/bundled/signals/get_signals_request.py deleted file mode 100644 index 0a1da82e..00000000 --- a/src/adcp/types/generated_poc/bundled/signals/get_signals_request.py +++ /dev/null @@ -1,1054 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/signals/get_signals_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class DiscoveryMode(StrEnum): - brief = 'brief' - wholesale = 'wholesale' - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, - Field( - description='Seller-assigned account identifier. For upstream-managed account namespaces, this value comes from list_accounts; for seller-defined namespaces without a list_accounts surface, it is supplied out-of-band. Buyer-declared account sellers MAY echo account_id from sync_accounts as an internal handle, but they MUST continue accepting the natural-key AccountRef for that account on subsequent calls.' - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent431(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent431 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account23(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - brand: Annotated[ - Brand, - Field( - description='Brand reference identifying the advertiser', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - operator: Annotated[ - str, - Field( - description="Domain of the entity operating on the brand's behalf. When the brand operates directly, this is the brand's domain.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - sandbox: Annotated[ - bool | None, - Field( - description='When true, references the sandbox account for this brand/operator pair. Defaults to false (production account).' - ), - ] = False - - -class SignalRefs1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRefs2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRefs3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRefs(RootModel[SignalRefs1 | SignalRefs2 | SignalRefs3]): - root: Annotated[ - SignalRefs1 | SignalRefs2 | SignalRefs3, - Field( - description="Reference to a named signal definition. Uses scope as discriminator: 'data_provider' for a signal resolved through published adagents.json signals[], 'signal_source' for a source-native signal resolved through the issuing signal source, or 'product' for a product-local signal option. Scope is the resolution path, not provenance; authoritative enrichment lives on the seller, signal source, or data-provider signal definition, not on this reference.", - discriminator='scope', - title='Signal Ref', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class SignalIds1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalIds2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalIds(RootModel[SignalIds1 | SignalIds2]): - root: Annotated[ - SignalIds1 | SignalIds2, - Field( - description="DEPRECATED. Use SignalRef for new discovery, activation, and media-buy signal identity surfaces. Legacy universal signal identifier used by older Signals Protocol clients. Uses 'source' as discriminator: 'catalog' for signals from a data provider's published catalog (verifiable), or 'agent' for signals native to a signal source identified by agent_url.", - discriminator='source', - title='Signal ID', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Destinations(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[ - str, - Field(description="Platform identifier for DSPs (e.g., 'the-trade-desk', 'amazon-dsp')"), - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the platform') - ] = None - - -class Destinations5(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, Field(description='URL identifying the deployment agent (for sales agents, etc.)') - ] - account: Annotated[ - str | None, Field(description='Optional account identifier on the agent') - ] = None - - -class Destinations3(RootModel[Destinations | Destinations5]): - root: Annotated[ - Destinations | Destinations5, - Field( - description='A deployment target where signals can be activated (DSP, sales agent, etc.)', - discriminator='type', - title='Destination', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class CatalogType(StrEnum): - marketplace = 'marketplace' - custom = 'custom' - owned = 'owned' - - -class Filters(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - catalog_types: Annotated[ - list[CatalogType] | None, Field(description='Filter by catalog type', min_length=1) - ] = None - data_providers: Annotated[ - list[str] | None, Field(description='Filter by specific data providers', min_length=1) - ] = None - max_cpm: Annotated[ - float | None, - Field(description="Maximum CPM filter. Applies only to signals with model='cpm'.", ge=0.0), - ] = None - max_percent: Annotated[ - float | None, - Field( - description='Maximum percent-of-media rate filter. Signals where all percent_of_media pricing options exceed this value are excluded. Does not account for max_cpm caps.', - ge=0.0, - le=100.0, - ), - ] = None - min_coverage_percentage: Annotated[ - float | None, Field(description='Minimum coverage requirement', ge=0.0, le=100.0) - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Vendor-namespaced extension parameters for seller- or platform-specific signal filter criteria not covered by standard fields. Keys MUST be namespaced under a vendor or platform key (e.g., ext.gam, ext.platform_x). Sellers MUST treat all values as untrusted buyer input; avoid unbounded logging or labels, and do not interpolate values into caller-visible error strings, LLM prompts, SQL queries, or system commands without sanitization. Persistent use of an extension key across multiple buyers is a signal to propose standardization.', - title='Extension Object', - ), - ] = None - - -class Field1(StrEnum): - signal_ref = 'signal_ref' - signal_id = 'signal_id' - signal_agent_segment_id = 'signal_agent_segment_id' - name = 'name' - description = 'description' - value_type = 'value_type' - categories = 'categories' - range = 'range' - signal_type = 'signal_type' - data_provider = 'data_provider' - coverage_percentage = 'coverage_percentage' - deployments = 'deployments' - pricing_options = 'pricing_options' - taxonomy = 'taxonomy' - data_sources = 'data_sources' - methodology = 'methodology' - segmentation_criteria = 'segmentation_criteria' - criteria_url = 'criteria_url' - refresh_cadence = 'refresh_cadence' - lookback_window = 'lookback_window' - onboarder = 'onboarder' - modeling = 'modeling' - audience_expansion = 'audience_expansion' - device_expansion = 'device_expansion' - countries = 'countries' - consent_basis = 'consent_basis' - restricted_attributes = 'restricted_attributes' - policy_categories = 'policy_categories' - art9_basis = 'art9_basis' - data_subject_rights = 'data_subject_rights' - last_updated = 'last_updated' - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - max_results: Annotated[ - int | None, Field(deprecated=True, - description='Maximum number of items to return per page', ge=1, le=100) - ] = 50 - cursor: Annotated[ - str | None, - Field(description='Opaque cursor from a previous response to fetch the next page'), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class GetSignalsRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - discovery_mode: Annotated[ - DiscoveryMode | None, - Field( - description="Declares caller intent for this request. 'brief' (default): semantic discovery — signal_spec, signal_refs, or legacy signal_ids is required and the agent performs inference/RAG. 'wholesale': raw wholesale signals feed enumeration — signal_spec, signal_refs, and signal_ids MUST NOT be provided and the agent returns its full priced signals feed, paginated, scoped by filters/account/destinations/countries when present. Sellers receiving requests from pre-v3.1 clients without discovery_mode MUST default to 'brief'. Timing semantics: 'wholesale' is a wholesale signals feed read — agents SHOULD respond synchronously and MUST NOT route a 'wholesale' request through the async/Submitted arm; partial completion is signalled via the response's incomplete[] field, not via a task-handoff envelope. Agents that do not implement wholesale enumeration MAY return INVALID_REQUEST for wholesale calls; callers SHOULD probe via get_adcp_capabilities (signals.discovery_modes) first." - ), - ] = DiscoveryMode.brief - account: Annotated[ - Account | Account23 | None, - Field( - description="Account for this request. When provided, the signals agent returns per-account pricing options if configured. In 'wholesale' mode, this is the rate-card scope: when omitted in wholesale mode, agents return their default rate-card pricing or omit pricing_options entirely.", - examples=[ - {'account_id': 'acc_acme_001'}, - {'brand': {'domain': 'acme-corp.com'}, 'operator': 'acme-corp.com'}, - { - 'brand': {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - 'operator': 'pinnacle-media.com', - }, - { - 'brand': {'domain': 'acme-corp.com'}, - 'operator': 'acme-corp.com', - 'sandbox': True, - }, - ], - title='Account Reference', - ), - ] = None - signal_spec: Annotated[ - str | None, - Field( - description="Natural language description of the desired signals. When used alone, enables semantic discovery. When combined with signal_refs, provides context for the agent but signal_ref matches are returned first. MUST NOT be provided when discovery_mode is 'wholesale'." - ), - ] = None - signal_refs: Annotated[ - list[SignalRefs] | None, - Field( - description="Specific signals to look up by reference. Returns exact matches for the requested SignalRef values. When combined with signal_spec, these signals anchor the starting set and signal_spec guides adjustments. MUST NOT be provided when discovery_mode is 'wholesale'.", - min_length=1, - ), - ] = None - signal_ids: Annotated[ - list[SignalIds] | None, - Field( - deprecated=True, - description="DEPRECATED. Use signal_refs instead. Legacy exact lookup field using SignalId objects. MUST NOT be provided when discovery_mode is 'wholesale'.", - min_length=1, - ), - ] = None - destinations: Annotated[ - list[Destinations3] | None, - Field( - description='Filter signals to those activatable on specific agents/platforms. When omitted, returns all signals available on the current agent. If the authenticated caller matches one of these destinations, activation keys will be included in the response.', - min_length=1, - ), - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries where signals will be used (ISO 3166-1 alpha-2 codes). When omitted, no geographic filter is applied.', - min_length=1, - ), - ] = None - filters: Annotated[ - Filters | None, - Field(description='Filters to refine signal discovery results', title='Signal Filters'), - ] = None - fields: Annotated[ - list[Field1] | None, - Field( - description="Specific signal fields to include in the response, aligned with get_products.fields. Required identity and activation fields such as signal_ref or signal_id, signal_agent_segment_id, name, description, signal_type, coverage_percentage, and deployments are always included when required by the response schema. Use for progressive disclosure of rich signal-definition metadata: request fields such as taxonomy, data_sources, methodology, segmentation_criteria, criteria_url, refresh_cadence, lookback_window, onboarder, modeling, audience_expansion, device_expansion, countries, consent_basis, restricted_attributes, policy_categories, art9_basis, data_subject_rights, and last_updated when the buyer needs them inline. Omit for the agent's default discovery projection. Agents SHOULD honor requested fields for exact lookup, refinement, small custom-signal result sets, and private/source-native signals when available. fields is a projection request, not an entitlement grant; agents MAY redact requested definition fields unless the caller is authorized for the underlying lineage, methodology, and rights-routing metadata. When consent_basis or art9_basis is projected for another provider's signal, the value remains provider-declared signal-definition posture; sellers and federating agents MUST NOT substitute their own processing basis. For broad discovery and wholesale pages, agents MAY return compact pointers instead of inlining large resources, especially when provider-published definitions can be resolved from signal_ref, taxonomy.ref, criteria_url, disclosure_url, and validators such as resolved URL plus catalog_etag, HTTP ETag/Last-Modified, or taxonomy.etag.", - min_length=1, - ), - ] = None - max_results: Annotated[ - int | None, - Field( - deprecated=True, - description='DEPRECATED: Use pagination.max_results instead. When both fields are present, agents MUST honor pagination.max_results. When only this field is present without a pagination envelope, agents SHOULD treat it as the page size subject to a maximum of 100 results. This field will be removed in AdCP 4.0.', - ge=1, - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Pagination parameters. Use pagination.max_results (max: 100, default: 50) and pagination.cursor for cursor-based page walks. When the deprecated top-level max_results field is also present, pagination.max_results takes precedence.', - title='Pagination Request', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Optional webhook configuration for async terminal completion/failure notifications on semantic signal discovery. Meaningful only for `discovery_mode: "brief"` requests that enter the async lifecycle. Submitted envelopes with `task_id` remain pollable through `get_task_status` (legacy `tasks/get`) whether or not this field is present. If a brief request includes this field and the agent returns a Submitted envelope, the agent MUST deliver at least the terminal completion/failure notification to the configured URL; intermediate progress notifications are MAY. If the agent cannot honor the webhook channel, it MUST reject the request with a structured error instead of silently accepting. This field does not change wholesale timing semantics: agents MUST NOT route `discovery_mode: "wholesale"` requests through the async/Submitted arm or emit async delivery solely because `push_notification_config` is present; partial wholesale completion is reported via `incomplete[]`.', - title='Push Notification Config', - ), - ] = None - if_wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque wholesale_feed_version token returned by a prior wholesale-mode get_signals response from this agent. Only valid when discovery_mode is wholesale. When provided, the agent compares against its current wholesale signals feed version for the caller's cache_scope and MAY return an unchanged: true response (with signals omitted) if nothing has changed. The token is scope-keyed: callers cache `(cache_scope, wholesale_feed_version)` pairs. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full sync pattern." - ), - ] = None - if_pricing_version: Annotated[ - str | None, - Field( - description="Opaque pricing_version token from a prior get_signals response. MUST only be sent together with if_wholesale_feed_version — pricing version has no structural baseline to compare against on its own. Evaluation order: (1) if_wholesale_feed_version mismatch → agent returns the full payload; (2) if_wholesale_feed_version matches but if_pricing_version mismatches → agent returns the full payload so the caller sees updated pricing_options; (3) both match → agent MAY return unchanged: true. Agents that don't track pricing separately ignore this and fall back to if_wholesale_feed_version semantics." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/signals/get_signals_response.py b/src/adcp/types/generated_poc/bundled/signals/get_signals_response.py deleted file mode 100644 index 7401e8c4..00000000 --- a/src/adcp/types/generated_poc/bundled/signals/get_signals_response.py +++ /dev/null @@ -1,2890 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/signals/get_signals_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from datetime import date as date_aliased -from adcp.types._str_enum import StrEnum -from collections.abc import Sequence -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field, RootModel - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SignalId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['catalog'], - Field( - description="Discriminator indicating this signal is from a data provider's published adagents.json signals[]" - ), - ] = 'catalog' - data_provider_domain: Annotated[ - str, - Field( - description="Domain of the data provider that owns this signal (e.g., 'pinnacle-data.example'). The signal definition is published at this domain's /.well-known/adagents.json", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's catalog (e.g., 'likely_ev_buyers', 'income_100k_plus')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalId9(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - source: Annotated[ - Literal['agent'], - Field( - description="Discriminator indicating this signal is native to the signal source identified by agent_url, not from a data provider's published signal definitions." - ), - ] = 'agent' - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the signal source that provides this signal (e.g., 'https://signals.example/.well-known/adcp/signals')" - ), - ] - id: Annotated[ - str, - Field( - description="Signal identifier within the agent's signal set (e.g., 'custom_auto_intenders')", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - - -class SignalRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['product'], - Field( - description="Discriminator indicating the signal resolves through the selected product's included_signals or signal_targeting_options." - ), - ] = 'product' - signal_id: Annotated[ - str, - Field( - description='Product-local signal identifier. For local signals exposed on both get_signals and get_products, this MUST match get_signals.signals[].signal_ref.signal_id for the same signal.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['data_provider'], - Field( - description="Discriminator indicating the signal resolves through a data provider's published adagents.json signals[]." - ), - ] = 'data_provider' - data_provider_domain: Annotated[ - str, - Field( - description='Domain that publishes the signal definition in its adagents.json signals[].', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the data provider's published adagents.json signals[].", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - -class SignalRef3(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - scope: Annotated[ - Literal['signal_source'], - Field( - description='Discriminator indicating the signal resolves through the issuing signal source.' - ), - ] = 'signal_source' - signal_source_url: Annotated[ - AnyUrl, Field(description='URL of the signal source that issues this source-native signal.') - ] - signal_id: Annotated[ - str, - Field( - description="Signal identifier within the issuing signal source's signal set.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - - - - -class Range(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - min: Annotated[float, Field(description='Minimum value, inclusive.')] - max: Annotated[float, Field(description='Maximum value, inclusive.')] - - -class SignalType(StrEnum): - marketplace = 'marketplace' - custom = 'custom' - owned = 'owned' - - -class GeoLevel(StrEnum): - country = 'country' - region = 'region' - metro = 'metro' - postal_area = 'postal_area' - - -class Dimensions(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['geo'], Field(description='Dimension family discriminator.')] = 'geo' - geo_level: Annotated[ - GeoLevel, - Field( - description='Geographic level for this forecast point.', - title='Geographic Targeting Level', - ), - ] - system: Annotated[ - str | None, - Field( - description="Classification system for metro or postal_area levels. Required when geo_level is 'metro' or 'postal_area'. Metro rows use metro-system enum values such as 'nielsen_dma'; native postal rows use country-local postal-system enum values such as 'zip' with country 'US'; deprecated legacy postal rows may use legacy-postal-system enum values such as 'us_zip'. Omit for country and region rows." - ), - ] = None - country: Annotated[ - str | None, - Field( - description='ISO 3166-1 alpha-2 country code. Required for native postal_area rows and omitted for legacy postal rows, metro rows, country rows, and region rows.', - pattern='^[A-Z]{2}$', - ), - ] = None - geo_code: Annotated[ - str, - Field( - description="Geographic code within the level and system. Country: ISO 3166-1 alpha-2 ('US'). Region: ISO 3166-2 with country prefix ('US-CA'). Metro/postal: system-specific code ('501', '10001')." - ), - ] - geo_name: Annotated[ - str | None, - Field( - description="Human-readable geographic name (e.g., 'United States', 'California', 'New York DMA')." - ), - ] = None - - -class PlacementRef(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - publisher_domain: Annotated[ - str | None, - Field( - description="Domain where the adagents.json declaring this placement is hosted. Omitted only for legacy single-publisher seller contexts where the seller agent's own publisher domain is the namespace.", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - placement_id: Annotated[ - str, - Field( - description="Placement ID from the publisher's adagents.json placement catalog, or an inline seller-defined placement ID interpreted within the same publisher namespace." - ), - ] - - -class Dimensions3(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['placement'], Field(description='Dimension family discriminator.')] = 'placement' - placement_ref: Annotated[ - PlacementRef, - Field( - description="Structured placement reference for this forecast row. References an entry from the product's placements array.", - title='Placement Reference', - ), - ] - placement_name: Annotated[ - str | None, - Field( - description='Human-readable placement name, useful when the buyer has not resolved the placement catalog.' - ), - ] = None - - -class DeviceType(StrEnum): - desktop = 'desktop' - mobile = 'mobile' - tablet = 'tablet' - ctv = 'ctv' - dooh = 'dooh' - unknown = 'unknown' - - -class Dimensions4(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['device_type'], Field(description='Dimension family discriminator.')] = 'device_type' - device_type: Annotated[ - DeviceType, - Field(description='Device form factor for this forecast row.', title='Device Type'), - ] - - -class DevicePlatform(StrEnum): - ios = 'ios' - android = 'android' - windows = 'windows' - macos = 'macos' - linux = 'linux' - chromeos = 'chromeos' - tvos = 'tvos' - tizen = 'tizen' - webos = 'webos' - fire_os = 'fire_os' - roku_os = 'roku_os' - unknown = 'unknown' - - -class Dimensions5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[ - Literal['device_platform'], Field(description='Dimension family discriminator.') - ] = 'device_platform' - device_platform: Annotated[ - DevicePlatform, - Field( - description='Operating system or platform for this forecast row.', - title='Device Platform', - ), - ] - - -class AudienceSource(StrEnum): - synced = 'synced' - platform = 'platform' - third_party = 'third_party' - lookalike = 'lookalike' - retargeting = 'retargeting' - unknown = 'unknown' - - -class Dimensions6(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['audience'], Field(description='Dimension family discriminator.')] = 'audience' - audience_id: Annotated[ - str, Field(description='Audience segment identifier for this forecast row.') - ] - audience_source: Annotated[ - AudienceSource, - Field(description='Origin of the audience segment.', title='Audience Source'), - ] - audience_name: Annotated[ - str | None, Field(description='Human-readable audience segment name.') - ] = None - - - - -class Presence(StrEnum): - present = 'present' - absent = 'absent' - - -class Dimensions7(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - kind: Annotated[Literal['signal'], Field(description='Dimension family discriminator.')] = 'signal' - signal_ref: Annotated[ - SignalRef | SignalRef2 | SignalRef3 | None, - Field( - description='Canonical signal reference for this forecast row. Required when the row needs to disambiguate product-local, data-provider, or signal-source identity. Product-relative forecasts SHOULD use signal_ref.', - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_id: Annotated[ - str | None, - Field( - description='Signal identifier shorthand for this forecast row. Use only when the enclosing context already identifies the signal unambiguously, such as a coverage_forecast nested directly under one get_signals signal item. Otherwise use signal_ref.', - pattern='^[a-zA-Z0-9_-]+$', - ), - ] = None - signal_value: Annotated[ - str | float | bool | None, - Field( - description="Signal value bucket represented by this point. Use null with presence 'absent' to represent inventory where the signal is not present. Omit when the row describes any present value rather than one specific value." - ), - ] = None - presence: Annotated[ - Presence, - Field( - description="Whether the signal is present for this point. Use 'absent' for the explicit not-present bucket." - ), - ] - signal_name: Annotated[ - str | None, - Field( - description='Human-readable signal name, useful when the buyer has not resolved the signal definition.' - ), - ] = None - signal_value_name: Annotated[ - str | None, Field(description='Human-readable label for the signal value bucket.') - ] = None - - -class AudienceSize(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0) - ] = None - - -class Reach(AudienceSize): - pass - - -class Frequency(AudienceSize): - pass - - -class Impressions(AudienceSize): - pass - - -class Clicks(AudienceSize): - pass - - -class Spend(AudienceSize): - pass - - -class Views(AudienceSize): - pass - - -class CompletedViews(AudienceSize): - pass - - -class Grps(AudienceSize): - pass - - -class Engagements(AudienceSize): - pass - - -class Follows(AudienceSize): - pass - - -class Saves(AudienceSize): - pass - - -class ProfileVisits(AudienceSize): - pass - - -class MeasuredImpressions(AudienceSize): - pass - - -class Downloads(AudienceSize): - pass - - -class Plays(AudienceSize): - pass - - -class CoverageRate(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - low: Annotated[ - float | None, Field(description='Conservative (low-end) forecast value', ge=0.0, le=1.0) - ] = None - mid: Annotated[ - float | None, Field(description='Expected (most likely) forecast value', ge=0.0, le=1.0) - ] = None - high: Annotated[ - float | None, Field(description='Optimistic (high-end) forecast value', ge=0.0, le=1.0) - ] = None - - -class Metrics(AdCPBaseModel): - audience_size: Annotated[ - AudienceSize | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - reach: Annotated[ - Reach | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - frequency: Annotated[ - Frequency | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - impressions: Annotated[ - Impressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - clicks: Annotated[ - Clicks | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - spend: Annotated[ - Spend | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - views: Annotated[ - Views | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - completed_views: Annotated[ - CompletedViews | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - grps: Annotated[ - Grps | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - engagements: Annotated[ - Engagements | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - follows: Annotated[ - Follows | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - saves: Annotated[ - Saves | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - profile_visits: Annotated[ - ProfileVisits | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - measured_impressions: Annotated[ - MeasuredImpressions | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - downloads: Annotated[ - Downloads | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - plays: Annotated[ - Plays | None, - Field( - description='A forecast value with optional confidence bounds. Either mid (point estimate) or both low and high (range) must be provided. mid represents the most likely outcome. low and high represent conservative and optimistic estimates. All three can be provided together.', - title='Forecast Range', - ), - ] = None - coverage_rate: Annotated[ - CoverageRate, - Field( - description="Share of the declared forecast scope represented by this point. For signal coverage forecasts, this is the point's count divided by the coverage_forecast.scope denominator. Range 0.0 to 1.0." - ), - ] - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class VerifyAgent433(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class MeasurableImpressions(AudienceSize): - pass - - -class ViewableImpressions(AudienceSize): - pass - - -class ViewableRate(CoverageRate): - pass - - -class ViewedSeconds(AudienceSize): - pass - - -class Standard(StrEnum): - mrc = 'mrc' - groupm = 'groupm' - - -class DeclaredBy217(DeclaredBy): - pass - - -class VerifyAgent434(VerifyAgent): - pass - - -class VerifyAgent435(VerifyAgent433): - pass - - -class VerificationItem217(VerificationItem): - pass - - -class Value(AudienceSize): - pass - - -class Method(StrEnum): - estimate = 'estimate' - modeled = 'modeled' - guaranteed = 'guaranteed' - - -class Kind(StrEnum): - inventory = 'inventory' - product = 'product' - account = 'account' - custom = 'custom' - - -class Country(RootModel[str]): - root: Annotated[str, Field(pattern='^[A-Z]{2}$')] - - -class DateRange(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - start: Annotated[date_aliased, Field(description='Start date (inclusive), ISO 8601')] - end: Annotated[date_aliased, Field(description='End date (inclusive), ISO 8601')] - - -class Scope(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - kind: Annotated[Kind, Field(description='Denominator family for the coverage forecast.')] - label: Annotated[ - str, - Field( - description="Human-readable denominator label, such as 'network price-priority inventory'." - ), - ] - product_id: Annotated[ - str | None, Field(description="Product denominator when kind is 'product'.") - ] = None - countries: Annotated[ - list[Country] | None, - Field( - description='Countries included in the denominator, as ISO 3166-1 alpha-2 codes.', - min_length=1, - ), - ] = None - line_item_types: Annotated[ - list[str] | None, - Field( - description='Seller or ad-server line item types included in the denominator.', - min_length=1, - ), - ] = None - date_range: Annotated[ - DateRange | None, - Field( - description='Historical or planned date window used to compute the denominator.', - title='Date Range', - ), - ] = None - - -class BucketSemantics(StrEnum): - exclusive = 'exclusive' - overlapping = 'overlapping' - - -class BucketCompleteness(StrEnum): - complete = 'complete' - partial = 'partial' - - -class ActivationKey(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['segment_id'], Field(description='Segment ID based targeting')] = 'segment_id' - segment_id: Annotated[ - str, - Field(description='The platform-specific segment identifier to use in campaign targeting'), - ] - - -class ActivationKey4(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Literal['key_value'], Field(description='Key-value pair based targeting')] = 'key_value' - key: Annotated[str, Field(description='The targeting parameter key')] - value: Annotated[str, Field(description='The targeting parameter value')] - - -class Deployments1(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['platform'], - Field(description='Discriminator indicating this is a platform-based deployment'), - ] = 'platform' - platform: Annotated[str, Field(description='Platform identifier for DSPs')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey4 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - - -class Deployments2(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Literal['agent'], - Field(description='Discriminator indicating this is an agent URL-based deployment'), - ] = 'agent' - agent_url: Annotated[AnyUrl, Field(description='URL identifying the deployment agent')] - account: Annotated[str | None, Field(description='Account identifier if applicable')] = None - is_live: Annotated[ - bool, Field(description='Whether signal is currently active on this deployment') - ] - activation_key: Annotated[ - ActivationKey | ActivationKey4 | None, - Field( - description='The key to use for targeting. Only present if is_live=true AND requester has access to this deployment.', - discriminator='type', - title='Activation Key', - ), - ] = None - estimated_activation_duration_minutes: Annotated[ - float | None, - Field( - description='Estimated time to activate if not live, or to complete activation if in progress', - ge=0.0, - ), - ] = None - deployed_at: Annotated[ - AwareDatetime | None, - Field(description='Timestamp when activation completed (if is_live=true)'), - ] = None - - -class Deployments(RootModel[Deployments1 | Deployments2]): - root: Annotated[ - Deployments1 | Deployments2, - Field( - description='A signal deployment to a specific deployment target with activation status and key', - discriminator='type', - title='Deployment', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class AppliesToOutputFormatId(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the agent that defines this format (e.g., 'https://creative.adcontextprotocol.org' for standard formats, or 'https://publisher.com/.well-known/adcp/sales' for custom formats). Callers comparing two `format-id` values MUST canonicalize `agent_url` per the AdCP URL canonicalization rules before treating two formats as the same. See docs/reference/url-canonicalization." - ), - ] - id: Annotated[ - str, - Field( - description="Format identifier within the agent's namespace (e.g., 'display_static', 'video_hosted', 'audio_standard'). When used alone, references a template format. When combined with dimension/duration fields, creates a parameterized format ID for a specific variant.", - pattern='^[a-zA-Z0-9_-]+$', - ), - ] - width: Annotated[ - int | None, - Field( - description='Width in pixels for visual formats. When specified, height must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - height: Annotated[ - int | None, - Field( - description='Height in pixels for visual formats. When specified, width must also be specified. Both fields together create a parameterized format ID for dimension-specific variants.', - ge=1, - ), - ] = None - duration_ms: Annotated[ - float | None, - Field( - description='Duration in milliseconds for time-based formats (video, audio). When specified, creates a parameterized format ID. Omit to reference a template format without parameters.', - ge=1.0, - ), - ] = None - - -class PricingOption131(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['cpm'] = 'cpm' - cpm: Annotated[float, Field(description='Cost per thousand impressions', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption132(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['percent_of_media'] = 'percent_of_media' - percent: Annotated[ - float, Field(description='Percentage of media spend, e.g. 15 = 15%', ge=0.0, le=100.0) - ] - max_cpm: Annotated[ - float | None, - Field( - description='Optional CPM cap. When set, the effective charge is min(percent × media_spend_per_mille, max_cpm).', - ge=0.0, - ), - ] = None - currency: Annotated[ - str, - Field(description='ISO 4217 currency code for the resulting charge', pattern='^[A-Z]{3}$'), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Period(StrEnum): - monthly = 'monthly' - quarterly = 'quarterly' - annual = 'annual' - campaign = 'campaign' - - -class PricingOption133(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['flat_fee'] = 'flat_fee' - amount: Annotated[float, Field(description='Fixed charge for the billing period', ge=0.0)] - period: Annotated[Period, Field(description='Billing period for the flat fee.')] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption134(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['per_unit'] = 'per_unit' - unit: Annotated[ - str, - Field( - description="What is counted — e.g. 'format', 'image', 'token', 'variant', 'render', 'evaluation'." - ), - ] - unit_price: Annotated[float, Field(description='Cost per one unit', ge=0.0)] - currency: Annotated[str, Field(description='ISO 4217 currency code', pattern='^[A-Z]{3}$')] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Metadata(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary_for_operator: Annotated[ - str | None, - Field( - description="One or two sentences describing the pricing construct in plain language, displayed to the buyer's operator when requesting approval. Should not repeat the top-level `description` verbatim — summarize the charge mechanic instead (e.g., 'Base $12 CPM plus $0.50 per qualifying post-view conversion, capped at $45 CPM').", - min_length=1, - ), - ] = None - - -class PricingOption135(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - model: Literal['custom'] = 'custom' - description: Annotated[ - str, - Field( - description='Human-readable description of the custom pricing model. Buyers display this to the operator when requesting approval.', - min_length=1, - ), - ] - metadata: Annotated[ - Metadata, - Field( - description="Structured parameters for the custom model. Keys follow lowercase_snake_case. Values may be primitives, arrays, or nested objects. Must be sufficient for a human to understand the pricing basis and for a downstream system to reconstruct the charge. Vendors SHOULD include a `summary_for_operator` string (one or two sentences, suitable for display in a buyer's operator-review UI) so reviewers across vendors see a consistent prompt. Required operator-review fields (approver role, dollar threshold for automatic approval, escalation contact) MAY be surfaced via additional keys the buyer's review surface recognizes." - ), - ] - currency: Annotated[ - str | None, - Field( - description='ISO 4217 currency code. Present when the pricing resolves to a monetary charge in a specific currency.', - pattern='^[A-Z]{3}$', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class PricingOption136(AdCPBaseModel): - pricing_option_id: Annotated[ - str, - Field( - description='Opaque identifier for this pricing option, unique within the vendor agent. Pass this in report_usage to identify which pricing option was applied.' - ), - ] - applies_to_output_format_ids: Annotated[ - list[AppliesToOutputFormatId] | None, - Field( - description='Creative transformers only: scopes this pricing option to specific output formats, so one transformer can price different outputs differently (e.g. a multi-publisher template charging per publisher format). When present, the option applies only to leaves whose format matches one of these; an unscoped option (this field absent) is the default for any output. A build targeting an output that matches no scoped option AND has no unscoped default is rejected with UNPRICEABLE_OUTPUT — never billed at a guessed rate (no silent fallback). Inert for non-creative vendors (signals/governance), which omit it.', - min_length=1, - ), - ] = None - - -class PricingOption137(PricingOption131, PricingOption136): - pass - - -class PricingOption138(PricingOption132, PricingOption136): - pass - - -class PricingOption139(PricingOption133, PricingOption136): - pass - - -class PricingOption1310(PricingOption134, PricingOption136): - pass - - -class PricingOption1311(PricingOption135, PricingOption136): - pass - - -class PricingOption( - RootModel[ - PricingOption137 - | PricingOption138 - | PricingOption139 - | PricingOption1310 - | PricingOption1311 - ] -): - root: Annotated[ - PricingOption137 - | PricingOption138 - | PricingOption139 - | PricingOption1310 - | PricingOption1311, - Field( - description='A pricing option offered by a vendor agent (signals, creative, governance). Combines pricing_option_id with the pricing model fields. Pass pricing_option_id in report_usage for billing verification. All vendor discovery responses return pricing_options as an array — vendors may offer multiple options (volume tiers, context-specific rates, different models per product line).', - title='Vendor Pricing Option', - ), - ] - def __getattr__(self, name: str) -> Any: - """Proxy attribute access to the wrapped type.""" - if name.startswith('_'): - raise AttributeError(name) - return getattr(self.root, name) - -class RestrictedAttribute(StrEnum): - racial_ethnic_origin = 'racial_ethnic_origin' - political_opinions = 'political_opinions' - religious_beliefs = 'religious_beliefs' - trade_union_membership = 'trade_union_membership' - health_data = 'health_data' - sex_life_sexual_orientation = 'sex_life_sexual_orientation' - genetic_data = 'genetic_data' - biometric_data = 'biometric_data' - age = 'age' - familial_status = 'familial_status' - - -class Value2(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - id: Annotated[str, Field(min_length=1)] - path: str | None = None - modifiers: list[str] | None = None - - -class ValueMapping(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - value: str - taxonomy_value_id: str - path: str | None = None - modifiers: list[str] | None = None - - -class ParentMatchBehavior(StrEnum): - exact_only = 'exact_only' - descendants_supported = 'descendants_supported' - unknown = 'unknown' - - -class Taxonomy(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - ref: AnyUrl - version: str | None = None - segtax: Annotated[int | None, Field(ge=1)] = None - etag: str | None = None - values: Annotated[list[Value2], Field(min_length=1)] - value_mappings: Annotated[list[ValueMapping] | None, Field(min_length=1)] = None - parent_match_behavior: ParentMatchBehavior | None = None - - -class DataSource(StrEnum): - app_behavior = 'app_behavior' - app_usage = 'app_usage' - web_usage = 'web_usage' - geo_location = 'geo_location' - email = 'email' - tv_ott_or_stb_device = 'tv_ott_or_stb_device' - panel = 'panel' - online_ecommerce = 'online_ecommerce' - credit_data = 'credit_data' - loyalty_card = 'loyalty_card' - transaction = 'transaction' - online_survey = 'online_survey' - offline_survey = 'offline_survey' - public_record_census = 'public_record_census' - public_record_voter_file = 'public_record_voter_file' - public_record_other = 'public_record_other' - offline_transaction = 'offline_transaction' - - -class Methodology(StrEnum): - observed = 'observed' - declared = 'declared' - derived = 'derived' - inferred = 'inferred' - modeled = 'modeled' - - -class RefreshCadence(StrEnum): - intra_day = 'intra_day' - daily = 'daily' - weekly = 'weekly' - monthly = 'monthly' - bi_monthly = 'bi_monthly' - quarterly = 'quarterly' - bi_annually = 'bi_annually' - annually = 'annually' - - -class MatchKey(StrEnum): - name = 'name' - address = 'address' - email = 'email' - postal = 'postal' - lat_long = 'lat_long' - mobile_id = 'mobile_id' - cookie_id = 'cookie_id' - ip = 'ip' - customer_id = 'customer_id' - phone = 'phone' - - -class PreOnboardingPrecisionLevel(StrEnum): - individual = 'individual' - household = 'household' - business = 'business' - geography = 'geography' - - -class Onboarder(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - match_keys: Annotated[list[MatchKey], Field(min_length=1)] - pre_onboarding_audience_expansion: bool | None = None - pre_onboarding_device_expansion: bool | None = None - pre_onboarding_precision_level: PreOnboardingPrecisionLevel | None = None - - -class ConsentBasi(StrEnum): - consent = 'consent' - legitimate_interest = 'legitimate_interest' - contract = 'contract' - legal_obligation = 'legal_obligation' - - -class Art9Basis(StrEnum): - explicit_consent = 'explicit_consent' - manifestly_made_public = 'manifestly_made_public' - substantial_public_interest = 'substantial_public_interest' - vital_interests = 'vital_interests' - - -class Method16(StrEnum): - lookalike = 'lookalike' - supervised = 'supervised' - embedding = 'embedding' - rules = 'rules' - - -class Type(StrEnum): - first_party_crm = 'first_party_crm' - panel = 'panel' - declared_survey = 'declared_survey' - transactional = 'transactional' - behavioral = 'behavioral' - - -class SeedSource(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - type: Type - provider_signed: Annotated[ - bool, - Field( - description='Provider assertion that the seed source carries a signed attestation. Consumers MUST NOT treat this boolean alone as cryptographic proof.' - ), - ] - - -class TrainingDataJurisdiction(Country): - pass - - -class AiActRiskClass(StrEnum): - minimal = 'minimal' - limited = 'limited' - high_risk = 'high_risk' - - -class Audience(StrEnum): - buyer = 'buyer' - data_subject = 'data_subject' - regulator = 'regulator' - public = 'public' - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - country: Annotated[ - str, Field(description='ISO 3166-1 alpha-2 country code.', pattern='^[A-Z]{2}$') - ] - region: Annotated[ - str | None, - Field( - description='Provider-defined sub-national region code or name when the obligation is regional. No global canonical format is implied.' - ), - ] = None - regulation: Annotated[ - str, - Field(description='Provider-supplied regulation identifier for the disclosure obligation.'), - ] - disclosure_text: Annotated[ - str | None, - Field( - description='Human-readable disclosure text or summary the provider expects buyers or reviewers to see.' - ), - ] = None - disclosure_url: Annotated[ - AnyUrl | None, - Field( - description="Optional URL to the provider's canonical disclosure or methodology page for this jurisdiction." - ), - ] = None - audience: Annotated[ - Audience | None, Field(description='Primary audience for this disclosure entry.') - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - required: Annotated[ - bool, - Field( - description="The provider's claim that a modeling or AI-use disclosure is required for this signal in at least one applicable jurisdiction. This is a declared compliance signal, not a protocol-level legal determination." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field( - description='Jurisdictions where a modeling or AI-use disclosure applies.', min_length=1 - ), - ] = None - notes: Annotated[ - str | None, - Field( - description='Optional provider notes on how the disclosure should be interpreted. Informational only; buyers should not branch programmatically on this text.', - max_length=2000, - ), - ] = None - - -class Modeling(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - method: Method16 - seed_source: SeedSource - training_data_jurisdictions: Annotated[list[TrainingDataJurisdiction], Field(min_length=1)] - ai_act_risk_class: AiActRiskClass - disclosure: Annotated[ - Disclosure | None, - Field( - description='Disclosure requirements and jurisdictional notes for modeled data signals. This schema is intentionally separate from core/provenance.json because creative provenance is about generated content, render guidance, and asset-level chain of custody, while signal modeling disclosure is about data-segment methodology and data-use transparency.', - title='Signal Modeling Disclosure', - ), - ] = None - - -class Right(StrEnum): - access = 'access' - rectification = 'rectification' - erasure = 'erasure' - portability = 'portability' - objection = 'objection' - - -class Channel(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - rights: Annotated[list[Right], Field(min_length=1)] - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - countries: list[Country] | None = None - - -class DataSubjectRights(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - upstream_source_domain: Annotated[ - str | None, - Field( - max_length=253, - pattern='^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]{0,61}[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]{0,61}[A-Za-z0-9])$', - ), - ] = None - channels: Annotated[list[Channel], Field(min_length=1)] - response_sla_days: Annotated[int | None, Field(ge=1, le=90)] = None - ccpa_opt_out_url: AnyUrl | None = None - - -class Issue15(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue15] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scope21(StrEnum): - signals = 'signals' - pricing = 'pricing' - wholesale_feed = 'wholesale_feed' - - -class Unit(StrEnum): - seconds = 'seconds' - minutes = 'minutes' - hours = 'hours' - days = 'days' - campaign = 'campaign' - - -class EstimatedWait(AdCPBaseModel): - interval: Annotated[ - int, Field(description="Number of time units. Must be 1 when unit is 'campaign'.", ge=1) - ] - unit: Annotated[ - Unit, - Field( - description="Time unit. 'seconds' for sub-minute precision. 'campaign' spans the full campaign flight." - ), - ] - - -class IncompleteItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - scope: Annotated[ - Scope21, - Field( - description="'signals': not all matching signals were returned. 'pricing': signals returned but pricing is absent or unconfirmed. 'wholesale_feed': in wholesale mode, full feed enumeration could not complete in the time budget." - ), - ] - description: Annotated[ - str, Field(description='Human-readable explanation of what is missing and why.') - ] - estimated_wait: Annotated[ - EstimatedWait | None, - Field( - description='How much additional time would resolve this scope. Allows the caller to decide whether to retry with a larger time_budget.' - ), - ] = None - - -class CacheScope(StrEnum): - public = 'public' - account = 'account' - - -class Pagination(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - has_more: Annotated[ - bool, Field(description='Whether more results are available beyond this page') - ] - cursor: Annotated[ - str | None, - Field( - description='Opaque cursor to pass in the next request to fetch the next page. Only present when has_more is true.' - ), - ] = None - total_count: Annotated[ - int | None, - Field( - description='Total number of items matching the query across all pages. Optional because not all backends can efficiently compute this.', - ge=0, - ), - ] = None - - -class SignalValueType(StrEnum): - binary = 'binary' - categorical = 'categorical' - numeric = 'numeric' - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class EmbeddedProvenanceMethod(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class WatermarkMediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class C2PAWatermarkAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class DisclosurePersistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class DisclosurePosition(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent433 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: DisclosurePersistence | None = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[DisclosurePosition] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction225(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure216(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction225] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure216 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Viewability(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - vendor: Annotated[ - Vendor | None, - Field( - description='Vendor expected to produce or verify these viewability values.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted impressions where viewability can be measured. Coverage denominator for viewable_rate and viewed_seconds.', - title='Forecast Range', - ), - ] = None - viewable_impressions: Annotated[ - ViewableImpressions | None, - Field( - description='Forecasted impressions expected to meet the viewability threshold defined by standard.', - title='Forecast Range', - ), - ] = None - viewable_rate: Annotated[ - ViewableRate | None, - Field( - description='Forecasted viewable impression rate (viewable_impressions / measurable_impressions). Range 0.0 to 1.0.' - ), - ] = None - viewed_seconds: Annotated[ - ViewedSeconds | None, - Field( - description='Forecasted average in-view duration per measurable impression, in seconds.', - title='Forecast Range', - ), - ] = None - standard: Annotated[ - Standard | None, - Field( - description='Viewability measurement standard applied to these forecasted values.', - title='Viewability Standard', - ), - ] = None - - -class EmbeddedProvenanceItem217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: EmbeddedProvenanceMethod - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent434 | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class Watermark217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: WatermarkMediaType - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent435 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: C2PAWatermarkAction | None = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Jurisdiction226(Jurisdiction225): - pass - - -class Disclosure217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction226] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Provenance217(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: DigitalSourceType | None = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy217 | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem217] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark217] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure217 | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem217] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance217 | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class BrandKitOverride23(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[ - Logo23 | None, Field(description='Override logo asset.', title='Image Asset') - ] = None - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Vendor5(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride23 | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class VendorMetricValue(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - vendor: Annotated[ - Vendor5, - Field( - description='Vendor that defines and forecasts this metric. Matches a reporting_capabilities.vendor_metrics declaration on the product.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - metric_id: Annotated[ - str, - Field( - description="Identifier for the metric within the vendor's vocabulary. Matches a vendor_metrics[].metric_id declaration on the product.", - examples=[ - 'attention_units', - 'gco2e_per_impression', - 'demographic_reach', - 'co_view_index', - 'incremental_lift_percent', - ], - max_length=64, - min_length=1, - pattern='^[a-z][a-z0-9_]*$', - title='Vendor Metric ID', - ), - ] - value: Annotated[ - Value, - Field( - description="Forecasted vendor metric value. Unit semantics are vendor-defined; see unit and the vendor's measurement-agent metric definition.", - title='Forecast Range', - ), - ] - unit: Annotated[ - str | None, - Field( - description="Unit of the value. Free-form to accommodate heterogeneous vendor metrics (e.g., 'score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'). When populated inline, SHOULD match the vendor's published unit.", - examples=['score', 'seconds', 'persons', 'gCO2e', 'USD', 'lift_percent', 'index'], - ), - ] = None - measurable_impressions: Annotated[ - MeasurableImpressions | None, - Field( - description='Forecasted number of impressions the vendor expects to be able to measure. Coverage denominator for the vendor metric; buyers compute estimated coverage as measurable_impressions / impressions when both are present.', - title='Forecast Range', - ), - ] = None - breakdown: Annotated[ - dict[str, Any] | None, - Field( - description="Optional structured payload for vendor metrics that do not fit a single scalar. Forecast rows SHOULD use ForecastRange values inside breakdown when sub-values are numeric forecasts. Buyers MUST treat this object as opaque without consulting the vendor's documentation." - ), - ] = None - - -class Point(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - label: Annotated[ - str | None, - Field( - description="Human-readable name for this forecast point. Required when forecast_range_unit is 'package' so buyer agents can identify and reference individual packages. Optional for other forecast types.", - examples=['Primetime', 'Morning Drive', 'Large Format Transit'], - max_length=128, - ), - ] = None - budget: Annotated[ - float | None, - Field( - description='Budget amount for this forecast point. Required for spend curves; omit for availability forecasts where the metrics represent total available inventory. For allocation-level forecasts, this is the absolute budget for that allocation (not the percentage). For proposal-level forecasts, this is the total proposal budget. When omitted, use metrics.spend to express the estimated cost of the available inventory.', - ge=0.0, - ), - ] = None - product_id: Annotated[ - str | None, - Field( - description='Optional product context for this forecast row. Usually omitted on product-level and allocation-level forecasts where the product is already implied. On proposal-level forecasts, populate when a dimensional row, especially a placement row, maps to a specific product allocation so buyers can turn the row into an executable package choice. Omit for true aggregate proposal rows spanning multiple products.' - ), - ] = None - dimensions: Annotated[ - list[Dimensions | Dimensions3 | Dimensions4 | Dimensions5 | Dimensions6 | Dimensions7], - Field( - description='Dimension constraints represented by this forecast point, such as country, region, placement, device type, platform, audience, signal value, or intersections such as placement x country or product x signal. Each item declares one dimension family; when multiple items are present, the point represents their intersection. Sellers MUST NOT emit more than one item for each `kind` on a point; consumers MUST NOT treat repeated kinds as OR semantics. Use multiple points with dimensions to expose country/placement/signal availability within one product, proposal, or signal coverage forecast without creating separate products solely for each dimension. Dimensions describe the forecast row and are independent of pricing_options.', - min_length=1, - title='Forecast Point Dimensions', - ), - ] - metrics: Annotated[ - Metrics, - Field( - description='Forecasted metric values. Keys are forecastable-metric enum values for delivery/engagement or event-type enum values for outcomes. Values are ForecastRange objects (low/mid/high). Use { "mid": value } for point estimates. When budget is present, these are the expected metrics at that spend level. When budget is omitted, these represent total available inventory — use spend to express the estimated cost. Additional keys beyond the documented properties are allowed for event-type values (purchase, lead, app_install, etc.).' - ), - ] - viewability: Annotated[ - Viewability | None, - Field( - description='Forecasted viewability metrics. Mirrors delivery-metrics.viewability, but numeric values are ForecastRange objects because forecast rows may provide low/mid/high bounds. Use this for pre-buy viewability expectations by forecast point without folding measurement metrics into pricing_options.' - ), - ] = None - vendor_metric_values: Annotated[ - list[VendorMetricValue] | None, - Field( - description="Forecasted values for vendor-defined metrics that the product's reporting_capabilities.vendor_metrics declared. Mirrors delivery-metrics.vendor_metric_values, but value and measurable_impressions use ForecastRange. These forecasted measurement values are independent of pricing_options." - ), - ] = None - - -class CoverageForecast(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - points: Annotated[ - list[Point], - Field( - description='Coverage or availability points. Each point reuses the standard ForecastPoint shape, MUST include a signal dimension, and MUST include metrics.coverage_rate. Use metrics.impressions for count denominators and metrics.coverage_rate for the fraction of the declared scope represented by the point.', - min_length=1, - ), - ] - forecast_range_unit: Annotated[ - Literal['availability'], - Field( - description="How to interpret the points array. Signal coverage forecasts always use 'availability' because the points describe available inventory or population coverage, not spend curves or temporal pacing." - ), - ] = 'availability' - method: Annotated[ - Method, - Field( - description='Method used to produce this coverage forecast.', title='Forecast Method' - ), - ] - scope: Annotated[ - Scope, - Field( - description='Explicit denominator for the coverage forecast. This identifies the inventory, product, account, or custom universe that coverage_rate values are relative to. Additional seller-specific qualifiers are allowed for scopes such as line item type, ad server, inventory class, country, or flight window.' - ), - ] - bucket_semantics: Annotated[ - BucketSemantics, - Field( - description="'exclusive' means the returned signal-value buckets do not overlap with each other. 'overlapping' means one impression or user can appear in multiple returned buckets, so coverage_rate values may sum above 1.0. This field describes overlap among returned buckets; bucket_completeness declares whether the returned buckets cover the full denominator." - ), - ] - bucket_completeness: Annotated[ - BucketCompleteness, - Field( - description="'complete' means the returned buckets cover the declared denominator. For complete + exclusive forecasts, count metrics and coverage_rate values can be treated as a full partition, subject to metric additivity rules. 'partial' means omitted denominator share represents undisclosed, other, or unsupported buckets; buyers MUST NOT infer totals by summing returned points." - ), - ] - generated_at: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast was computed.') - ] = None - valid_until: Annotated[ - AwareDatetime | None, Field(description='When this coverage forecast expires.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Signal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - signal_id: Annotated[ - SignalId | SignalId9 | SignalId | SignalId9 | None, - Field( - deprecated=True, - description='DEPRECATED. Use signal_ref instead. Legacy SignalId retained for compatibility with older Signals Protocol clients.', - discriminator='source', - title='Signal ID', - ), - ] = None - signal_ref: Annotated[ - SignalRef | SignalRef2 | SignalRef3 | SignalRef | SignalRef2 | SignalRef3 | None, - Field( - description="Canonical signal reference. Use scope 'product' for a product-local signal defined by this listing; use scope 'data_provider' with data_provider_domain for a signal defined in a data provider's published adagents.json signals[]; use scope 'signal_source' with signal_source_url for a source-native signal.", - discriminator='scope', - title='Signal Ref', - ), - ] = None - signal_agent_segment_id: Annotated[ - str, - Field( - description='Opaque resolved-segment handle issued by this signal source. Pass this string verbatim to activate_signal.signal_agent_segment_id, and echo it in package signal targeting when the selected product option exposes the same handle. Treat the value as provider-scoped and opaque: providers MAY namespace it so two providers can expose similarly named signals without relying on a shared taxonomy. Do not pass the signal_id object as this handle, and do not reconstruct a segment handle from categorical values when get_signals returned a resolved segment.' - ), - ] - name: Annotated[ - str, - Field( - description="Human-readable signal name. Required when signal_ref.scope is 'product'. For data_provider and signal_source refs, this is optional contextual display text; the referenced definition or source remains authoritative." - ), - ] - description: Annotated[ - str, - Field( - description='Detailed signal description. For data_provider and signal_source refs, this is optional contextual display text and MUST NOT replace the referenced definition.' - ), - ] - value_type: SignalValueType | None = None - categories: Annotated[ - list[str] | None, - Field( - description="Valid values for categorical signals. Present when value_type is 'categorical'.", - min_length=1, - ), - ] = None - range: Annotated[ - Range | None, - Field(description="Valid range for numeric signals. Present when value_type is 'numeric'."), - ] = None - signal_type: Annotated[ - SignalType, - Field( - description='Commercial/provenance type of signal (marketplace, custom, owned)', - title='Signal Availability Type', - ), - ] - data_provider: Annotated[ - str | None, - Field( - description='Human-readable source name for the signal, when applicable. For data_provider-scoped signals this is the data provider name; for signal_source-scoped signals it may identify the signal source or proprietary origin.' - ), - ] = None - coverage_percentage: Annotated[ - float | None, - Field( - deprecated=True, - description='DEPRECATED for detailed planning. Optional legacy scalar percentage of audience coverage retained only as a fallback for clients that do not consume coverage_forecast. When coverage_forecast is present, coverage_forecast is authoritative for signal-level discovery and coverage_percentage is fallback-only. If coverage_forecast includes an absent bucket over the same denominator, coverage_percentage SHOULD align with 100 * (1 - absent coverage_rate.mid).', - ge=0.0, - le=100.0, - ), - ] = None - coverage_forecast: Annotated[ - CoverageForecast | None, - Field( - description='Optional forecast-shaped signal availability guidance. When present, this is authoritative for signal-level discovery coverage. Use this to disclose the denominator, bucket semantics, not-present bucket, aggregate present bucket, and per-value coverage distribution for the signal.', - title='Signal Coverage Forecast', - ), - ] = None - deployments: Annotated[Sequence[Deployments], Field(description='Array of deployment targets')] - pricing_options: Annotated[ - list[PricingOption] | None, - Field( - description='Pricing options available for this signal when it has an incremental price. The buyer selects one and passes its pricing_option_id in report_usage or package-level signal_targeting_groups for billing verification. Omit when pricing is unavailable to the caller, bundled into the destination product, or has no incremental cost.', - min_length=1, - ), - ] = None - methodology_url: Annotated[ - AnyUrl | None, - Field( - description='Optional link to published methodology, media-kit, or data documentation. For data_provider and signal_source refs, this SHOULD match or supplement the referenced definition.' - ), - ] = None - last_updated: Annotated[ - AwareDatetime | None, - Field( - description='When this definition record was last updated. This indicates freshness of the definition record, not an attestation that the underlying data or model was refreshed at that time.' - ), - ] = None - restricted_attributes: Annotated[ - list[RestrictedAttribute] | None, - Field(description='Restricted attribute categories this signal touches.', min_length=1), - ] = None - policy_categories: Annotated[ - list[str] | None, - Field(description='Policy categories this signal is sensitive for.', min_length=1), - ] = None - taxonomy: Annotated[ - Taxonomy | None, - Field( - description='Optional taxonomy metadata describing what this signal means in an external audience, content, retail-media, or provider-owned taxonomy.' - ), - ] = None - segmentation_criteria: Annotated[str | None, Field(max_length=500)] = None - criteria_url: AnyUrl | None = None - data_sources: Annotated[list[DataSource] | None, Field(min_length=1)] = None - methodology: Methodology | None = None - audience_expansion: bool | None = None - device_expansion: bool | None = None - refresh_cadence: RefreshCadence | None = None - lookback_window: RefreshCadence | None = None - onboarder: Onboarder | None = None - countries: Annotated[list[Country] | None, Field(min_length=1)] = None - consent_basis: Annotated[ - list[ConsentBasi] | None, - Field( - description="Data provider's declared GDPR Article 6 lawful basis or consent basis for the underlying signal definition, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own processing basis for the provider-declared basis.", - min_length=1, - ), - ] = None - art9_basis: Annotated[ - Art9Basis | None, - Field( - description="Data provider's declared GDPR Article 9 basis for the underlying signal definition when special-category data is involved and Article 9 applies, projected into this get_signals response row when requested. Sellers and federating agents that pass through another provider's signal MUST NOT substitute their own Article 9 basis for the provider-declared basis." - ), - ] = None - modeling: Modeling | None = None - data_subject_rights: Annotated[ - DataSubjectRights | None, - Field( - description='Per-signal data-subject-rights routing. This is a contact/routing reference, not a machine-callable AdCP API.' - ), - ] = None - dts_compliant_version: str | None = None - - -class GetSignalsResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - signals: Annotated[Sequence[Signal] | None, Field(description='Array of matching signals')] = None - errors: Annotated[ - list[Error] | None, - Field( - description='Task-specific errors and warnings (e.g., signal discovery or pricing issues)' - ), - ] = None - incomplete: Annotated[ - list[IncompleteItem] | None, - Field( - description="Declares what the agent could not finish within the caller's time_budget or due to internal limits. Each entry identifies a scope that is missing or partial. Absent when the response is fully complete.", - min_length=1, - ), - ] = None - wholesale_feed_version: Annotated[ - str | None, - Field( - description="Opaque token representing the version of the wholesale signals feed state used to compose this response. Agents that implement conditional-fetch (if_wholesale_feed_version) MUST return this on every wholesale-mode response so callers can cache and probe later. Callers MUST treat the value as opaque — no format, no ordering, no inspection. The token is scope-keyed: it describes a version for the cache_scope declared on this response, NOT a global agent version. A caller caches `(cache_scope, wholesale_feed_version)` pairs and presents the matching token on the next request. Scoping dimensions: (agent, discovery_mode, filters, destinations, countries) for cache_scope: 'public'; that tuple plus account_id for cache_scope: 'account'. pagination.cursor is NOT part of the scoping tuple. See specs/wholesale-feed-webhooks.md for the full cache layering model." - ), - ] = None - pricing_version: Annotated[ - str | None, - Field( - description='Opaque token representing the version of the pricing layer. When the agent supports independent pricing versioning, pricing_version changes when prices move but wholesale_feed_version changes only when structure/metadata moves. Same cache_scope keying as wholesale_feed_version. Agents not separating these MAY omit pricing_version and use wholesale_feed_version for both.' - ), - ] = None - cache_scope: Annotated[ - CacheScope | None, - Field( - description="Declares whether the wholesale_feed_version and pricing_version on this response describe a universal layer or an account-specific overlay. REQUIRED on every 3.1+ response (the 3.1 schema enforces this — the safety property of the two-layer cache model depends on it). 'public': this response describes the agent's published rate card; the caller MAY dedupe under (agent, discovery_mode, filters, destinations, countries) without scoping by account. 'account': this response includes account-specific overrides; the caller MUST cache the version under that tuple plus account_id. When the request did NOT include `account`, the agent MUST return `cache_scope: 'public'`. When the request included `account`, the agent MUST return either 'public' (this account prices off the public rate card — caller dedupes) or 'account' (account-specific overrides exist — caller caches under the account key). Agents MAY return 'public' on an account-scoped request that previously had overrides — callers SHOULD interpret this as a downgrade. Without schema-required cache_scope, an agent silently omitting the field on an account-scoped response would cause callers to mis-key the cache and serve account-overlay payloads to other accounts — the canonical safety invariant of the entire cache layering model. **Backward-compatibility note for 3.1 validators:** SDKs validating strictly against the 3.1 schema MUST select the validator based on the server-declared `adcp_version`. For responses with `adcp_version` starting `3.0`, the 3.1 cache_scope-required constraint MUST be relaxed — pre-3.1 agents correctly emit no cache_scope and remain conformant to their declared version. This is a tightening within 3.1, not a 3.0 break." - ), - ] = CacheScope.public - unchanged: Annotated[ - Literal[True] | None, - Field( - description="Present and `true` ONLY on wholesale-mode responses when the request carried if_wholesale_feed_version (and/or if_pricing_version) matching the agent's current version for the caller's cache_scope, in which case signals[] MUST be omitted; wholesale_feed_version (echoed), cache_scope (echoed), and pricing_version (echoed when used) MUST still be present. Callers receiving unchanged: true MUST NOT mutate their local wholesale signals mirror. **One shape per state:** agents MUST NOT emit `unchanged: false` — the absence of the field IS the signal that the response carries signals." - ), - ] = None - pagination: Annotated[ - Pagination | None, - Field( - description='Standard cursor-based pagination metadata for list responses', - title='Pagination Response', - ), - ] = None - sandbox: Annotated[ - bool | None, - Field(description='When true, this response contains simulated data from sandbox mode.'), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/__init__.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/__init__.py deleted file mode 100644 index bcbe0473..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -# generated by datamodel-codegen: -# filename: .schema_temp -# timestamp: 2026-04-18T19:31:26+00:00 diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_request.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_request.py deleted file mode 100644 index ea04be71..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_request.py +++ /dev/null @@ -1,61 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_get_offering_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class SiGetOfferingRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - offering_id: Annotated[ - str, Field(description='Offering identifier from the catalog to get details for') - ] - intent: Annotated[ - str | None, - Field( - description="Optional natural language description of user intent for personalized results (e.g., 'mens size 14 near Cincinnati'). Must be anonymous - no PII." - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - include_products: Annotated[ - bool | None, Field(description='Whether to include matching products in the response') - ] = False - product_limit: Annotated[ - int | None, Field(description='Maximum number of matching products to return', ge=1, le=50) - ] = 5 - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_response.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_response.py deleted file mode 100644 index 94effa54..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_get_offering_response.py +++ /dev/null @@ -1,1142 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_get_offering_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1587(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1587 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - Brand, - Field( - description='Brand economically accountable for the sponsored context.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class ContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction827(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction827] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role838(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy794(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role838, Field(description='Role of the declaring party.')] - - -class SponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: Annotated[ - ContextUse, - Field( - description='Declared host-side use mode for this sponsored context.', - title='SI Context Use', - ), - ] - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy794 | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Issue80(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue80] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class OfferingAvailabilityStatus(StrEnum): - available = 'available' - limited = 'limited' - sold_out = 'sold_out' - expired = 'expired' - region_restricted = 'region_restricted' - inactive = 'inactive' - - -class Offering(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - offering_id: Annotated[str | None, Field(description='Offering identifier')] = None - title: Annotated[str | None, Field(description='Offering title')] = None - summary: Annotated[str | None, Field(description='Brief summary of the offering')] = None - tagline: Annotated[str | None, Field(description='Short promotional tagline')] = None - expires_at: Annotated[AwareDatetime | None, Field(description='When this offering expires')] = ( - None - ) - availability_status: OfferingAvailabilityStatus | None = None - price_hint: Annotated[ - str | None, Field(description="Price indication (e.g., 'from $199', '50% off')") - ] = None - image_url: Annotated[AnyUrl | None, Field(description='Hero image for the offering')] = None - landing_url: Annotated[AnyUrl | None, Field(description='Landing page URL')] = None - - -class MatchingProduct(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - product_id: Annotated[str, Field(description='Product identifier')] - name: Annotated[str, Field(description='Product name')] - price: Annotated[str | None, Field(description="Display price (e.g., '$129', '$89.99')")] = None - original_price: Annotated[str | None, Field(description='Original price if on sale')] = None - image_url: Annotated[AnyUrl | None, Field(description='Product image')] = None - availability_summary: Annotated[ - str | None, - Field( - description="Brief availability info (e.g., 'In stock', 'Size 14 available', '3 left')" - ), - ] = None - availability_status: OfferingAvailabilityStatus | None = None - url: Annotated[AnyUrl | None, Field(description='Product detail page URL')] = None - - -class SiGetOfferingResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - available: Annotated[bool, Field(description='Whether the offering is currently available')] - offering_token: Annotated[ - str | None, - Field( - description="Token to pass to si_initiate_session for session continuity. Brand stores the full query context server-side (products shown, order, context) so they can resolve references like 'the second one' when the session starts." - ), - ] = None - ttl_seconds: Annotated[ - int | None, - Field( - description='How long this offering information is valid (seconds). Host should re-fetch after TTL expires.', - ge=0, - ), - ] = None - checked_at: Annotated[ - AwareDatetime | None, Field(description='When this offering information was retrieved') - ] = None - offering: Annotated[Offering | None, Field(description='Offering details')] = None - matching_products: Annotated[ - list[MatchingProduct] | None, - Field( - description='Products matching the request context. Only included if include_products was true.' - ), - ] = None - sponsored_context: Annotated[ - SponsoredContext | None, - Field( - description='Declaration for the sponsored context carried by this offering response. When present, it applies to the returned offering and matching_products package as a whole unless a future extension narrows the declaration to individual items. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before using it.', - title='SI Sponsored Context', - ), - ] = None - total_matching: Annotated[ - int | None, - Field( - description='Total number of products matching the context (may be more than returned in matching_products)', - ge=0, - ), - ] = None - unavailable_reason: Annotated[ - str | None, - Field( - description="If not available, why (e.g., 'expired', 'sold_out', 'region_restricted')" - ), - ] = None - alternative_offering_ids: Annotated[ - list[str] | None, - Field(description='Alternative offerings to consider if this one is unavailable'), - ] = None - errors: Annotated[list[Error] | None, Field(description='Errors during offering lookup')] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_request.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_request.py deleted file mode 100644 index aaec9f4b..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_request.py +++ /dev/null @@ -1,1024 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_initiate_session_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class ConsentScopeEnum(StrEnum): - name = 'name' - email = 'email' - shipping_address = 'shipping_address' - phone = 'phone' - locale = 'locale' - - -class PrivacyPolicyAcknowledged(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand_policy_url: Annotated[ - AnyUrl | None, Field(description="URL to brand's privacy policy") - ] = None - brand_policy_version: Annotated[ - str | None, Field(description='Version of policy acknowledged') - ] = None - - -class ShippingAddress(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - street: str | None = None - city: str | None = None - state: str | None = None - postal_code: str | None = None - country: str | None = None - - -class User(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - email: Annotated[EmailStr | None, Field(description="User's email address")] = None - name: Annotated[str | None, Field(description="User's display name")] = None - locale: Annotated[str | None, Field(description="User's locale (e.g., en-US)")] = None - phone: Annotated[str | None, Field(description="User's phone number")] = None - shipping_address: Annotated[ - ShippingAddress | None, Field(description="User's shipping address for accurate pricing") - ] = None - - -class Identity(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - consent_granted: Annotated[bool, Field(description='Whether user consented to share identity')] - consent_timestamp: Annotated[ - AwareDatetime | None, Field(description='When consent was granted (ISO 8601)') - ] = None - consent_scope: Annotated[ - list[ConsentScopeEnum] | None, Field(description='What data was consented to share') - ] = None - privacy_policy_acknowledged: Annotated[ - PrivacyPolicyAcknowledged | None, Field(description='Brand privacy policy acknowledgment') - ] = None - user: Annotated[ - User | None, Field(description='User data (only present if consent_granted is true)') - ] = None - anonymous_session_id: Annotated[ - str | None, - Field(description='Session ID for anonymous users (when consent_granted is false)'), - ] = None - - -class Voice(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='TTS provider (elevenlabs, openai, etc.)') - ] = None - voice_id: Annotated[str | None, Field(description='Brand voice identifier')] = None - - -class Video(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - formats: Annotated[ - list[str] | None, Field(description='Supported video formats (mp4, webm, etc.)') - ] = None - max_duration_seconds: Annotated[int | None, Field(description='Maximum video duration')] = None - - -class Avatar(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='Avatar provider (d-id, heygen, synthesia, etc.)') - ] = None - avatar_id: Annotated[str | None, Field(description='Brand avatar identifier')] = None - - -class Modalities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - conversational: Annotated[ - bool | None, Field(description='Pure text exchange - the baseline modality') - ] = True - voice: Annotated[ - bool | Voice | None, Field(description='Audio-based interaction using brand voice') - ] = None - video: Annotated[bool | Video | None, Field(description='Brand video content playback')] = None - avatar: Annotated[ - bool | Avatar | None, Field(description='Animated video presence with brand avatar') - ] = None - - -class StandardEnum(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - - -class Components(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - standard: Annotated[ - list[StandardEnum] | None, - Field(description='Standard components that all SI hosts must render'), - ] = None - extensions: Annotated[ - dict[str, Any] | None, - Field(description='Platform-specific extensions (chatgpt_apps_sdk, maps, forms, etc.)'), - ] = None - - -class Commerce(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - acp_checkout: Annotated[ - bool | None, Field(description='Supports ACP (Agentic Commerce Protocol) checkout handoff') - ] = None - - -class A2ui(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[bool | None, Field(description='Supports A2UI surface rendering')] = False - catalogs: Annotated[ - list[str] | None, - Field(description="Supported A2UI component catalogs (e.g., 'si-standard', 'standard')"), - ] = None - - -class SupportedCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - modalities: Annotated[ - Modalities | None, Field(description='Interaction modalities supported') - ] = None - components: Annotated[Components | None, Field(description='Visual components supported')] = ( - None - ) - commerce: Annotated[Commerce | None, Field(description='Commerce capabilities')] = None - a2ui: Annotated[A2ui | None, Field(description='A2UI (Agent-to-UI) capabilities')] = None - mcp_apps: Annotated[ - bool | None, Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes') - ] = False - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1589(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1589 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - Brand, - Field( - description='Brand economically accountable for the sponsored context.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction829(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction829] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role840(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy796(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role840, Field(description='Role of the declaring party.')] - - -class Status(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class Status198(StrEnum): - accepted = 'accepted' - not_required = 'not_required' - - -class DisclosureCommitment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status198, - Field( - description="Host commitment status for the disclosure obligation. Use accepted when the declaration requires disclosure and the host will satisfy it; use not_required only when the declaration's disclosure_obligation.required is false. A host that will not satisfy a required disclosure rejects the sponsored context." - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host committed to render, if different from or copied from the declaration.' - ), - ] = None - notes: Annotated[ - str | None, Field(description='Optional host explanation for the disclosure commitment.') - ] = None - - -class SIContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' - - -class SponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: SIContextUse - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy796 | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HostReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field(description='Whether the host accepted the declared sponsored context for use.'), - ] - accepted_context_use: SIContextUse | None = None - received_at: Annotated[ - AwareDatetime, Field(description='When the host received the sponsored context.') - ] - host_surface: Annotated[ - str | None, - Field( - description='Host-defined surface or placement where the context was accepted, such as an assistant session, search result page, or comparison module.' - ), - ] = None - disclosure_commitment: Annotated[ - DisclosureCommitment | None, - Field( - description='How the host committed to handle the declared disclosure obligation. Required when host_receipt.status is accepted.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description='Optional explanation when status is rejected, for example unsupported context_use or inability to satisfy the disclosure obligation.' - ), - ] = None - - -class SponsoredContextReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - sponsored_context: Annotated[ - SponsoredContext, - Field( - description='The sponsored-context declaration the host received and is accepting or rejecting.', - title='SI Sponsored Context', - ), - ] - host_receipt: Annotated[ - HostReceipt, - Field( - description='Receiving-surface accountability fact: the use mode the host accepted and committed to honor for this sponsored context.' - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class SiInitiateSessionRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - intent: Annotated[ - str, - Field( - description='Natural language description of user intent — the conversation handoff from the host describing what the user needs from the brand agent' - ), - ] - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - identity: Annotated[ - Identity, - Field( - description='User identity shared with brand agent (with explicit consent)', - title='SI Identity', - ), - ] - media_buy_id: Annotated[ - str | None, Field(description='AdCP media buy ID if session was triggered by advertising') - ] = None - placement: Annotated[ - str | None, - Field( - description="Where this session was triggered (e.g., 'chatgpt_search', 'claude_chat')" - ), - ] = None - offering_id: Annotated[ - str | None, Field(description='Brand-specific offering identifier to apply') - ] = None - supported_capabilities: Annotated[ - SupportedCapabilities | None, - Field(description='What capabilities the host supports', title='SI Capabilities'), - ] = None - offering_token: Annotated[ - str | None, - Field( - description="Token from si_get_offering response for session continuity. Brand uses this to recall what products were shown to the user, enabling natural references like 'the second one' or 'that blue shoe'." - ), - ] = None - sponsored_context_receipt: Annotated[ - SponsoredContextReceipt | None, - Field( - description='Host receipt for sponsored context accepted from a prior si_get_offering response or other pre-session context package. This records the accepted context_use, disclosure commitment, paying_principal, and host receipt for audit.', - title='SI Sponsored Context Receipt', - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for this request. Prevents duplicate session creation on retries. MUST be unique per (seller, request) pair to prevent cross-seller correlation. Use a fresh UUID v4 for each request.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_response.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_response.py deleted file mode 100644 index eeaee937..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_initiate_session_response.py +++ /dev/null @@ -1,1225 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_initiate_session_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Type(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - app_handoff = 'app_handoff' - integration_actions = 'integration_actions' - - -class UiElement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Type, Field(description='Component type')] - data: Annotated[dict[str, Any] | None, Field(description='Component-specific data')] = None - - -class Response(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( - None - ) - ui_elements: Annotated[ - list[UiElement] | None, Field(description='Visual components to render') - ] = None - - -class Voice(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='TTS provider (elevenlabs, openai, etc.)') - ] = None - voice_id: Annotated[str | None, Field(description='Brand voice identifier')] = None - - -class Video(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - formats: Annotated[ - list[str] | None, Field(description='Supported video formats (mp4, webm, etc.)') - ] = None - max_duration_seconds: Annotated[int | None, Field(description='Maximum video duration')] = None - - -class Avatar(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - provider: Annotated[ - str | None, Field(description='Avatar provider (d-id, heygen, synthesia, etc.)') - ] = None - avatar_id: Annotated[str | None, Field(description='Brand avatar identifier')] = None - - -class Modalities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - conversational: Annotated[ - bool | None, Field(description='Pure text exchange - the baseline modality') - ] = True - voice: Annotated[ - bool | Voice | None, Field(description='Audio-based interaction using brand voice') - ] = None - video: Annotated[bool | Video | None, Field(description='Brand video content playback')] = None - avatar: Annotated[ - bool | Avatar | None, Field(description='Animated video presence with brand avatar') - ] = None - - -class StandardEnum(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - - -class Components(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - standard: Annotated[ - list[StandardEnum] | None, - Field(description='Standard components that all SI hosts must render'), - ] = None - extensions: Annotated[ - dict[str, Any] | None, - Field(description='Platform-specific extensions (chatgpt_apps_sdk, maps, forms, etc.)'), - ] = None - - -class Commerce(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - acp_checkout: Annotated[ - bool | None, Field(description='Supports ACP (Agentic Commerce Protocol) checkout handoff') - ] = None - - -class A2ui(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - supported: Annotated[bool | None, Field(description='Supports A2UI surface rendering')] = False - catalogs: Annotated[ - list[str] | None, - Field(description="Supported A2UI component catalogs (e.g., 'si-standard', 'standard')"), - ] = None - - -class NegotiatedCapabilities(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - modalities: Annotated[ - Modalities | None, Field(description='Interaction modalities supported') - ] = None - components: Annotated[Components | None, Field(description='Visual components supported')] = ( - None - ) - commerce: Annotated[Commerce | None, Field(description='Commerce capabilities')] = None - a2ui: Annotated[A2ui | None, Field(description='A2UI (Agent-to-UI) capabilities')] = None - mcp_apps: Annotated[ - bool | None, Field(description='Supports MCP Apps for rendering A2UI surfaces in iframes') - ] = False - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1591(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1591 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - Brand, - Field( - description='Brand economically accountable for the sponsored context.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class ContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction831(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction831] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role842(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy798(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role842, Field(description='Role of the declaring party.')] - - -class SponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: Annotated[ - ContextUse, - Field( - description='Declared host-side use mode for this sponsored context.', - title='SI Context Use', - ), - ] - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy798 | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class SessionStatus(StrEnum): - active = 'active' - pending_handoff = 'pending_handoff' - complete = 'complete' - terminated = 'terminated' - - -class Issue82(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue82] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class SiInitiateSessionResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - session_id: Annotated[ - str, Field(description='Unique session identifier for subsequent messages') - ] - response: Annotated[Response | None, Field(description="Brand agent's initial response")] = None - negotiated_capabilities: Annotated[ - NegotiatedCapabilities | None, - Field( - description='Intersection of brand and host capabilities for this session', - title='SI Capabilities', - ), - ] = None - sponsored_context: Annotated[ - SponsoredContext | None, - Field( - description='Declaration for sponsored context carried by the initial brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.', - title='SI Sponsored Context', - ), - ] = None - session_status: Annotated[ - SessionStatus, - Field( - description='Current session lifecycle state. Returned in initiation, message, and termination responses.', - title='SI Session Status', - ), - ] - session_ttl_seconds: Annotated[ - int | None, - Field( - description='Session inactivity timeout in seconds. After this duration without a message, the brand agent may terminate the session. Hosts SHOULD warn users before timeout when possible.', - ge=1, - ), - ] = None - errors: Annotated[list[Error] | None, Field(description='Errors during session initiation')] = ( - None - ) - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_request.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_request.py deleted file mode 100644 index 3cd0f268..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_request.py +++ /dev/null @@ -1,832 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_send_message_request.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class ActionResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[str | None, Field(description='The action that was triggered')] = None - payload: Annotated[ - dict[str, Any] | None, Field(description='Action-specific response data') - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1593(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1593 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - Brand, - Field( - description='Brand economically accountable for the sponsored context.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction833(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction833] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role844(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy800(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role844, Field(description='Role of the declaring party.')] - - -class Status(StrEnum): - accepted = 'accepted' - rejected = 'rejected' - - -class Status201(StrEnum): - accepted = 'accepted' - not_required = 'not_required' - - -class DisclosureCommitment(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status201, - Field( - description="Host commitment status for the disclosure obligation. Use accepted when the declaration requires disclosure and the host will satisfy it; use not_required only when the declaration's disclosure_obligation.required is false. A host that will not satisfy a required disclosure rejects the sponsored context." - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host committed to render, if different from or copied from the declaration.' - ), - ] = None - notes: Annotated[ - str | None, Field(description='Optional host explanation for the disclosure commitment.') - ] = None - - -class SIContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' - - -class SponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: SIContextUse - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy800 | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class HostReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - status: Annotated[ - Status, - Field(description='Whether the host accepted the declared sponsored context for use.'), - ] - accepted_context_use: SIContextUse | None = None - received_at: Annotated[ - AwareDatetime, Field(description='When the host received the sponsored context.') - ] - host_surface: Annotated[ - str | None, - Field( - description='Host-defined surface or placement where the context was accepted, such as an assistant session, search result page, or comparison module.' - ), - ] = None - disclosure_commitment: Annotated[ - DisclosureCommitment | None, - Field( - description='How the host committed to handle the declared disclosure obligation. Required when host_receipt.status is accepted.' - ), - ] = None - rejection_reason: Annotated[ - str | None, - Field( - description='Optional explanation when status is rejected, for example unsupported context_use or inability to satisfy the disclosure obligation.' - ), - ] = None - - -class SponsoredContextReceipt(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - sponsored_context: Annotated[ - SponsoredContext, - Field( - description='The sponsored-context declaration the host received and is accepting or rejecting.', - title='SI Sponsored Context', - ), - ] - host_receipt: Annotated[ - HostReceipt, - Field( - description='Receiving-surface accountability fact: the use mode the host accepted and committed to honor for this sponsored context.' - ), - ] - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class SiSendMessageRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - idempotency_key: Annotated[ - str, - Field( - description='Client-generated unique key for at-most-once execution. Each conversational turn is a distinct mutation of session transcript — without this key, a timeout-and-retry produces a duplicate turn and a duplicate model response. MUST be unique per (seller, request) pair. Use a fresh UUID v4 for each user turn.', - max_length=255, - min_length=16, - pattern='^[A-Za-z0-9_.:-]{16,255}$', - ), - ] - session_id: Annotated[str, Field(description='Active session identifier')] - message: Annotated[str | None, Field(description="User's message to the brand agent")] = None - action_response: Annotated[ - ActionResponse | None, - Field(description='Response to a previous action_button (e.g., user clicked checkout)'), - ] = None - sponsored_context_receipt: Annotated[ - SponsoredContextReceipt | None, - Field( - description="Host receipt for sponsored context accepted from a prior SI response in this session. This gives the brand/seller an audit-visible record of the host's accepted use mode and disclosure commitment for that context.", - title='SI Sponsored Context Receipt', - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_response.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_response.py deleted file mode 100644 index 1f08610e..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_send_message_response.py +++ /dev/null @@ -1,1206 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_send_message_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any, Literal - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, EmailStr, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class Component(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - id: Annotated[str, Field(description='Unique identifier for this component within the surface')] - parentId: Annotated[ - str | None, Field(description='ID of the parent component (null for root)') - ] = None - component: Annotated[ - dict[str, dict[str, Any]], - Field(description='Component definition (keyed by component type)'), - ] - - -class Surface(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - surfaceId: Annotated[str, Field(description='Unique identifier for this surface')] - catalogId: Annotated[ - str | None, Field(description='Component catalog to use for rendering') - ] = 'standard' - components: Annotated[ - list[Component], Field(description='Flat list of components (adjacency list structure)') - ] - rootId: Annotated[ - str | None, - Field(description='ID of the root component (if not specified, first component is root)'), - ] = None - dataModel: Annotated[ - dict[str, Any] | None, Field(description='Application data that components can bind to') - ] = None - - -class Type(StrEnum): - text = 'text' - link = 'link' - image = 'image' - product_card = 'product_card' - carousel = 'carousel' - action_button = 'action_button' - app_handoff = 'app_handoff' - integration_actions = 'integration_actions' - - -class UiElement(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[Type, Field(description='Component type')] - data: Annotated[dict[str, Any] | None, Field(description='Component-specific data')] = None - - -class Response(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - message: Annotated[str | None, Field(description='Conversational message from brand agent')] = ( - None - ) - surface: Annotated[ - Surface | None, - Field(description='A2UI surface with interactive components', title='A2UI Surface'), - ] = None - ui_elements: Annotated[ - list[UiElement] | None, - Field( - deprecated=True, - description='Visual components to render (DEPRECATED: use surface instead)', - ), - ] = None - - -class DataSubjectContestation(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - url: AnyUrl | None = None - email: EmailStr | None = None - languages: list[str] | None = None - - -class DigitalSourceType(StrEnum): - digital_capture = 'digital_capture' - digital_creation = 'digital_creation' - trained_algorithmic_media = 'trained_algorithmic_media' - composite_with_trained_algorithmic_media = 'composite_with_trained_algorithmic_media' - algorithmic_media = 'algorithmic_media' - composite_capture = 'composite_capture' - composite_synthetic = 'composite_synthetic' - human_edits = 'human_edits' - data_driven_media = 'data_driven_media' - - -class AiTool(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - name: Annotated[ - str, - Field( - description="Name of the AI tool or model (e.g., 'DALL-E 3', 'Stable Diffusion XL', 'Gemini')" - ), - ] - version: Annotated[ - str | None, - Field( - description="Version identifier for the AI tool or model (e.g., '25.1', '0125', '2.1'). For generative models, use the model version rather than the API version." - ), - ] = None - provider: Annotated[ - str | None, - Field( - description="Organization that provides the AI tool (e.g., 'OpenAI', 'Stability AI', 'Google')" - ), - ] = None - - -class HumanOversight(StrEnum): - none = 'none' - prompt_only = 'prompt_only' - selected = 'selected' - edited = 'edited' - directed = 'directed' - - -class Role(StrEnum): - creator = 'creator' - advertiser = 'advertiser' - agency = 'agency' - platform = 'platform' - tool = 'tool' - - -class DeclaredBy(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, - Field(description='URL of the agent or service that declared this provenance'), - ] = None - role: Annotated[Role, Field(description='Role of the declaring party in the supply chain')] - - -class C2pa(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - manifest_url: Annotated[ - AnyUrl, Field(description='URL to the C2PA manifest store for this content') - ] - - -class Method(StrEnum): - manifest_wrapper = 'manifest_wrapper' - provenance_markers = 'provenance_markers' - - -class VerifyAgent(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to embed/verify this layer. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `encypher.markers_present_v2`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class EmbeddedProvenanceItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - method: Annotated[ - Method, - Field( - description='How provenance data is carried within the content', - title='Embedded Provenance Method', - ), - ] - standard: Annotated[ - str | None, - Field( - description="Standard the embedding conforms to, if any (e.g., 'c2pa' for C2PA Section A.7 text manifest embedding)" - ), - ] = None - provider: Annotated[ - str, - Field( - description="Organization that performed the embedding (e.g., 'Encypher', 'Digimarc'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent | None, - Field( - description="Buyer's representation that this embedding can be verified by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist). MAY be omitted for self-verifiable embeddings (e.g., a C2PA text manifest with a public key the seller already trusts)." - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the provenance data was embedded (ISO 8601)') - ] = None - - -class MediaType(StrEnum): - audio = 'audio' - image = 'image' - video = 'video' - text = 'text' - - -class VerifyAgent1595(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - agent_url: Annotated[ - AnyUrl, - Field( - description="URL of the governance agent the buyer represents was used to apply/detect this watermark. MUST use the `https://` scheme and MUST appear in the seller's `creative_policy.accepted_verifiers[].agent_url` list (canonicalized per /docs/reference/url-canonicalization: lowercase scheme and host, strip default port, normalize path dot-segments). Sellers MUST NOT call this URL until the canonicalized match is confirmed." - ), - ] - feature_id: Annotated[ - str | None, - Field( - description="Optional `feature_id` the buyer represents the seller should request via `get_creative_features` (e.g., `imatag.watermark_detected`). SHOULD match the `feature_id` declared on the matching `accepted_verifiers[]` entry, or be omitted to defer the selector to the seller. When the seller's entry pins a `feature_id`, that value wins; when neither side pins, the seller selects from the agent's `governance.creative_features` catalog." - ), - ] = None - - -class C2paAction(StrEnum): - c2pa_watermarked_bound = 'c2pa.watermarked.bound' - c2pa_watermarked_unbound = 'c2pa.watermarked.unbound' - - -class Watermark(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - media_type: Annotated[ - MediaType, - Field( - description='Media category of the watermarked content', title='Watermark Media Type' - ), - ] - provider: Annotated[ - str, - Field( - description="Organization that applied the watermark (e.g., 'Imatag', 'Steg.AI', 'Encypher'). Display label and audit context — not a wire identifier." - ), - ] - verify_agent: Annotated[ - VerifyAgent1595 | None, - Field( - description="Buyer's representation that this watermark can be detected by a governance agent on the seller's `creative_policy.accepted_verifiers` list. The `agent_url` MUST match (canonicalized) one of the seller's published `accepted_verifiers[].agent_url` entries; sellers reject `sync_creatives` submissions whose `verify_agent.agent_url` is off-list with `PROVENANCE_VERIFIER_NOT_ACCEPTED`. This is buyer-supplied evidence, not buyer-driven routing — the seller is the verifier-of-record and the seller controls which agent it actually calls (the seller MAY use a different on-list agent if it determines this is more appropriate; the seller does not call buyer-asserted endpoints outside its allowlist)." - ), - ] = None - c2pa_action: Annotated[ - C2paAction | None, - Field( - description='C2PA action classification for this watermark', - title='C2PA Watermark Action', - ), - ] = None - embedded_at: Annotated[ - AwareDatetime | None, Field(description='When the watermark was applied (ISO 8601)') - ] = None - - -class Persistence(StrEnum): - continuous = 'continuous' - initial = 'initial' - flexible = 'flexible' - - -class Position(StrEnum): - prominent = 'prominent' - footer = 'footer' - audio = 'audio' - subtitle = 'subtitle' - overlay = 'overlay' - end_card = 'end_card' - pre_roll = 'pre_roll' - companion = 'companion' - - -class RenderGuidance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - persistence: Annotated[ - Persistence | None, - Field( - description='How long the disclosure must persist during content playback or display', - title='Disclosure Persistence', - ), - ] = None - min_duration_ms: Annotated[ - int | None, - Field( - description="Minimum display duration in milliseconds for initial persistence. Recommended when persistence is initial — without it, the duration is at the publisher's discretion. At serve time the publisher reads this from provenance since the brief is not available.", - ge=1, - ), - ] = None - positions: Annotated[ - list[Position] | None, - Field( - description='Preferred disclosure positions in priority order. The first position a format supports should be used.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Jurisdiction(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[ - str, Field(description="ISO 3166-1 alpha-2 country code (e.g., 'US', 'DE', 'CN')") - ] - region: Annotated[ - str | None, - Field(description="Sub-national region code (e.g., 'CA' for California, 'BY' for Bavaria)"), - ] = None - regulation: Annotated[ - str, - Field( - description="Regulation identifier (e.g., 'eu_ai_act_article_50', 'ca_sb_942', 'cn_deep_synthesis')" - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Required disclosure label text for this jurisdiction, in the local language' - ), - ] = None - render_guidance: Annotated[ - RenderGuidance | None, - Field( - description="How the disclosure should be rendered for this jurisdiction. Expresses the declaring party's intent for persistence and position based on regulatory requirements. Publishers control actual rendering but governance agents can audit whether guidance was followed." - ), - ] = None - - -class Disclosure(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description="The declaring party's claim that AI disclosure is required for this content under applicable regulations. This is a declared signal carried through the supply chain — useful as a routing and audit input — not a regulatory determination made by the protocol. Receiving parties remain responsible for their own jurisdictional analysis and should not treat `required: false` as compliance cover." - ), - ] - jurisdictions: Annotated[ - list[Jurisdiction] | None, - Field(description='Jurisdictions where disclosure obligations apply', min_length=1), - ] = None - - -class Result(StrEnum): - authentic = 'authentic' - ai_generated = 'ai_generated' - ai_modified = 'ai_modified' - inconclusive = 'inconclusive' - - -class VerificationItem(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - verified_by: Annotated[ - str, - Field( - description="Name of the verification service (e.g., 'DoubleVerify', 'Hive Moderation', 'Reality Defender')" - ), - ] - verified_time: Annotated[ - AwareDatetime | None, Field(description='When the verification was performed (ISO 8601)') - ] = None - result: Annotated[Result, Field(description='Verification outcome')] - confidence: Annotated[ - float | None, - Field( - description='Confidence score of the verification result (0.0 to 1.0)', ge=0.0, le=1.0 - ), - ] = None - details_url: Annotated[ - AnyUrl | None, Field(description='URL to the full verification report') - ] = None - - -class Provenance(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - digital_source_type: Annotated[ - DigitalSourceType | None, - Field( - description='IPTC-aligned classification of AI involvement in producing this content', - title='Digital Source Type', - ), - ] = None - ai_tool: Annotated[ - AiTool | None, - Field( - description='AI system used to generate or modify this content. Aligns with IPTC 2025.1 AI metadata fields and C2PA claim_generator.' - ), - ] = None - human_oversight: Annotated[ - HumanOversight | None, - Field( - description='Level of human involvement in the AI-assisted creation process. Independent of `disclosure.required` — the protocol does not derive disclosure obligations from oversight level. Some regulations include carve-outs for human-edited or human-directed AI output, but those carve-outs have factual prerequisites the schema cannot evaluate. Asserting `edited` or `directed` does not by itself justify `disclosure.required: false`.' - ), - ] = None - declared_by: Annotated[ - DeclaredBy | None, - Field( - description='Party declaring this provenance. Identifies who attached the provenance claim, enabling receiving parties to assess trust.' - ), - ] = None - declared_at: Annotated[ - AwareDatetime | None, - Field( - description='When this provenance claim was made (ISO 8601). Distinct from created_time, which records when the content itself was produced. A provenance claim may be attached well after content creation, for example when retroactively declaring AI involvement for regulatory compliance.' - ), - ] = None - created_time: Annotated[ - AwareDatetime | None, - Field(description='When this content was created or generated (ISO 8601)'), - ] = None - c2pa: Annotated[ - C2pa | None, - Field( - description='C2PA sidecar manifest reference. Links to a detached cryptographic provenance manifest for this content. Note: file-level C2PA bindings break when ad servers transcode, resize, or re-encode assets. For pipelines with intermediaries, consider embedded_provenance as the primary provenance mechanism.' - ), - ] = None - embedded_provenance: Annotated[ - list[EmbeddedProvenanceItem] | None, - Field( - description='Provenance metadata embedded within the content stream. Each entry declares one embedding layer: structured provenance data carried inside the content itself, as distinct from sidecar references (c2pa.manifest_url). Embedded provenance survives operations that break sidecar and file-level bindings: ad-server transcoding, CMS ingestion, copy-paste, reformatting, and CDN re-encoding. For ad-tech pipelines where content passes through multiple intermediaries, embedded provenance is the reliable path for provenance that persists from declaration through delivery. This is a declaration by the embedding party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - watermarks: Annotated[ - list[Watermark] | None, - Field( - description='Content watermarks applied to this asset. Each entry declares one watermarking layer: a content modification that encodes an identifier or fingerprint within the asset. Watermarks differ from embedded provenance: a watermark encodes an identifier (who generated it, who owns it), while embedded provenance carries or references a structured provenance record (the full chain of custody). A single asset may carry both. Aligns with C2PA action taxonomy: c2pa.watermarked.bound (watermark linked to a C2PA manifest) and c2pa.watermarked.unbound (watermark independent of any manifest). This is a declaration by the watermarking party. The receiving party (the seller) is the verifier-of-record: it confirms the claim by calling a governance agent it trusts (typically one published in `creative_policy.accepted_verifiers`).', - min_length=1, - ), - ] = None - disclosure: Annotated[ - Disclosure | None, - Field( - description='Regulatory disclosure requirements for this content. Indicates whether AI disclosure is required and under which jurisdictions.' - ), - ] = None - verification: Annotated[ - list[VerificationItem] | None, - Field( - description='Third-party verification or detection results for this content. Multiple services may independently evaluate the same content. Provenance is a claim — verification results attached by the declaring party are supplementary. The enforcing party (e.g., seller/publisher) should run its own verification via get_creative_features or calibrate_content.', - min_length=1, - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class Logo(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - asset_type: Annotated[ - Literal['image'], - Field( - description='Discriminator identifying this as an image asset. See /schemas/creative/asset-types for the registry.' - ), - ] = 'image' - url: Annotated[AnyUrl, Field(description='URL to the image asset')] - width: Annotated[int, Field(description='Width in pixels', ge=1)] - height: Annotated[int, Field(description='Height in pixels', ge=1)] - format: Annotated[ - str | None, Field(description='Image file format (jpg, png, gif, webp, etc.)') - ] = None - alt_text: Annotated[str | None, Field(description='Alternative text for accessibility')] = None - provenance: Annotated[ - Provenance | None, - Field( - description='Provenance metadata for this asset, overrides manifest-level provenance', - title='Provenance', - ), - ] = None - - -class Colors(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - primary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - secondary: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - accent: Annotated[str | None, Field(pattern='^#[0-9a-fA-F]{6}$')] = None - - -class BrandKitOverride(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - logo: Annotated[Logo | None, Field(description='Override logo asset.', title='Image Asset')] = ( - None - ) - colors: Annotated[Colors | None, Field(description='Override brand colors (hex strings).')] = ( - None - ) - voice: Annotated[ - str | None, - Field( - description='Override brand-voice description for surface-composed text/audio output.' - ), - ] = None - tagline: Annotated[str | None, Field(description='Override tagline.')] = None - - -class Brand(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - domain: Annotated[ - str, - Field( - description="Domain where /.well-known/brand.json is hosted, or the brand's operating domain", - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] - brand_id: Annotated[ - str | None, - Field( - description='Brand identifier within the house portfolio. Optional for single-brand domains.', - examples=['tide', 'cheerios', 'air_jordan', 'nike', 'pampers'], - pattern='^[a-z0-9_]+$', - title='Brand ID', - ), - ] = None - industries: Annotated[ - list[str] | None, - Field( - description="Inline override for the brand's industries. Useful when the caller cannot modify the brand's canonical brand.json but needs to declare industries for governance (e.g., Annex III vertical detection). brand.json remains the canonical source; when omitted here, governance agents SHOULD resolve from brand.json." - ), - ] = None - data_subject_contestation: Annotated[ - DataSubjectContestation | None, - Field( - description="Inline override for the brand's contestation contact point. Useful when the operator does not control brand.json but needs to discharge Art 22(3) for this plan. brand.json is canonical; when omitted, governance agents resolve brand → house → missing." - ), - ] = None - brand_kit_override: Annotated[ - BrandKitOverride | None, - Field( - description="Inline override for brand-kit fields normally resolved from `/.well-known/brand.json` on `domain` (logo, colors, voice, tagline). Use when brand.json is missing, stale, or inappropriate for this specific call — e.g., a campaign-scoped tagline, a co-branded creative, a freshly-rebranded color palette the brand.json hasn't shipped yet. Same inline-override pattern as `industries` and `data_subject_contestation` above: brand.json is canonical, the override is per-call. Adopters needing to override fields outside this subset (`voice_attributes`, `prohibited_terms`, etc.) MUST publish a different brand.json and reference it via a different `domain` — the inline override is intentionally narrow to a small high-traffic subset.\n\n**Merge semantics (normative).** The merge is **field-level**, not whole-object replacement. Each field within `brand_kit_override` (`logo`, `colors`, `voice`, `tagline`) is evaluated independently — when a field is present on the override the override value applies; when a field is absent the brand.json value applies (or is absent if brand.json doesn't carry one either). For composite fields (`colors.primary`, `colors.secondary`, `colors.accent`), the merge is one level deeper: each color slot is evaluated independently — a producer can override `colors.primary` while still inheriting `colors.secondary` from brand.json. SDKs MUST NOT treat a present `brand_kit_override.colors` as wiping the brand.json `colors` block entirely; only the per-slot fields present in the override take precedence. Without this rule, a partial-override semantics would diverge across SDKs and produce inconsistent rendering for the same payload." - ), - ] = None - - -class Account(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - account_id: Annotated[ - str, Field(description='Seller-assigned account identifier for the paying principal.') - ] - - -class PayingPrincipal(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - brand: Annotated[ - Brand, - Field( - description='Brand economically accountable for the sponsored context.', - examples=[ - {'domain': 'nova-brands.com', 'brand_id': 'spark'}, - {'domain': 'nova-brands.com', 'brand_id': 'glow'}, - {'domain': 'acme-corp.com'}, - ], - title='Brand Reference', - ), - ] - account: Annotated[ - Account | None, - Field( - description='Optional seller-assigned account context for the paying principal. This intentionally carries only an account_id so the canonical economic principal remains paying_principal.brand.' - ), - ] = None - operator: Annotated[ - str | None, - Field( - description='Domain of the operator acting for the paying principal, when different from the brand domain.', - pattern='^[a-z0-9]([a-z0-9-]*[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$', - ), - ] = None - display_name: Annotated[ - str | None, - Field( - description='Human-readable label for disclosure rendering. The canonical identity remains the brand/account reference.' - ), - ] = None - - -class ContextUse(StrEnum): - presentation_only = 'presentation_only' - comparison_set = 'comparison_set' - reasoning_context = 'reasoning_context' - - -class Timing(StrEnum): - before_use = 'before_use' - at_first_influenced_output = 'at_first_influenced_output' - near_each_influenced_output = 'near_each_influenced_output' - - -class Proximity(StrEnum): - session_level = 'session_level' - near_rendered_unit = 'near_rendered_unit' - near_influenced_output = 'near_influenced_output' - - -class Jurisdiction835(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - country: Annotated[str, Field(description='ISO 3166-1 alpha-2 country code.')] - region: Annotated[str | None, Field(description='Optional sub-national region code.')] = None - regulation: Annotated[str, Field(description='Regulation or policy identifier.')] - - -class DisclosureObligation(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - required: Annotated[ - bool, - Field( - description='Whether the declaring party requires disclosure for this sponsored context.' - ), - ] - label_text: Annotated[ - str | None, - Field( - description='Disclosure label text the host should render when disclosure is required.' - ), - ] = None - timing: Annotated[ - Timing | None, - Field( - description="When the disclosure must be presented relative to the sponsored context's influence." - ), - ] = None - proximity: Annotated[ - Proximity | None, - Field(description='Where the disclosure should appear relative to the affected output.'), - ] = None - jurisdictions: Annotated[ - list[Jurisdiction835] | None, - Field( - description='Jurisdictions where this declared disclosure obligation applies.', - min_length=1, - ), - ] = None - - -class Role846(StrEnum): - brand_agent = 'brand_agent' - seller = 'seller' - network = 'network' - platform = 'platform' - - -class DeclaredBy802(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - agent_url: Annotated[ - AnyUrl | None, Field(description='HTTPS URL of the declaring agent or service.') - ] = None - role: Annotated[Role846, Field(description='Role of the declaring party.')] - - -class SponsoredContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - paying_principal: Annotated[ - PayingPrincipal, - Field( - description="Economic accountability fact: the brand that funded or sponsored this context, with optional account/operator context. This identifies who paid for the sponsored context; it is distinct from the host's later receipt and use commitment." - ), - ] - context_use: Annotated[ - ContextUse, - Field( - description='Declared host-side use mode for this sponsored context.', - title='SI Context Use', - ), - ] - disclosure_obligation: Annotated[ - DisclosureObligation, - Field( - description='Disclosure obligation the receiving host must either accept and satisfy or reject before using this sponsored context. This is a declared obligation and audit input, not a protocol-level legal determination.' - ), - ] - declared_at: Annotated[ - AwareDatetime | None, Field(description='When this sponsored-context declaration was made.') - ] = None - declared_by: Annotated[ - DeclaredBy802 | None, Field(description='Agent or service that attached the declaration.') - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None - - -class SessionStatus(StrEnum): - active = 'active' - pending_handoff = 'pending_handoff' - complete = 'complete' - terminated = 'terminated' - - -class Type40(StrEnum): - transaction = 'transaction' - complete = 'complete' - - -class Price(AdCPBaseModel): - amount: float | None = None - currency: str | None = None - - -class Intent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Annotated[str | None, Field(description="The commerce action (e.g., 'purchase')")] = ( - None - ) - product: Annotated[dict[str, Any] | None, Field(description='Product details for checkout')] = ( - None - ) - price: Annotated[Price | None, Field(description='Price information')] = None - - -class ContextForCheckout(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - conversation_summary: Annotated[ - str | None, Field(description='Summary of the conversation leading to purchase') - ] = None - applied_offers: Annotated[ - list[str] | None, Field(description='Offer IDs that were applied during the conversation') - ] = None - - -class Handoff(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - type: Annotated[ - Type40 | None, - Field( - description='Type of handoff: transaction (ready for ACP checkout) or complete (conversation done)' - ), - ] = None - intent: Annotated[ - Intent | None, - Field(description='For transaction handoffs: what the user wants to purchase'), - ] = None - context_for_checkout: Annotated[ - ContextForCheckout | None, Field(description='Context to pass to ACP for seamless checkout') - ] = None - - -class Issue84(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue84] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class SiSendMessageResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - session_id: Annotated[str, Field(description='Session identifier')] - response: Annotated[Response | None, Field(description="Brand agent's response")] = None - mcp_resource_uri: Annotated[ - str | None, - Field( - description='MCP resource URI for hosts with MCP Apps support (e.g., ui://si/session-abc123)' - ), - ] = None - sponsored_context: Annotated[ - SponsoredContext | None, - Field( - description='Declaration for sponsored context carried by this brand-agent response. Hosts MUST either honor the declared context_use and disclosure_obligation or reject the context before presenting, comparing, or otherwise using it.', - title='SI Sponsored Context', - ), - ] = None - session_status: Annotated[ - SessionStatus, - Field( - description='Current session status. On a successful response, one of: active, pending_handoff, or complete. Terminated sessions return error codes (SESSION_NOT_FOUND or SESSION_TERMINATED) instead of a success response.', - title='SI Session Status', - ), - ] - handoff: Annotated[ - Handoff | None, Field(description='Handoff request when session_status is pending_handoff') - ] = None - errors: list[Error] | None = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_request.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_request.py deleted file mode 100644 index 725f6d79..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_request.py +++ /dev/null @@ -1,87 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_terminate_session_request.json -# timestamp: 2026-05-22T13:15:12+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import ConfigDict, Field - - -class Reason(StrEnum): - handoff_transaction = 'handoff_transaction' - handoff_complete = 'handoff_complete' - user_exit = 'user_exit' - session_timeout = 'session_timeout' - host_terminated = 'host_terminated' - - -class Action(StrEnum): - purchase = 'purchase' - subscribe = 'subscribe' - - -class TransactionIntent(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Action | None = None - product: Annotated[dict[str, Any] | None, Field(description='Product/service details')] = None - - -class TerminationContext(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - summary: Annotated[str | None, Field(description='Summary of the conversation')] = None - transaction_intent: Annotated[ - TransactionIntent | None, - Field(description='For handoff_transaction - what user wants to buy'), - ] = None - cause: Annotated[ - str | None, Field(description='For host_terminated - why host ended session') - ] = None - - -class SiTerminateSessionRequest(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - session_id: Annotated[str, Field(description='Session identifier to terminate')] - reason: Annotated[Reason, Field(description='Reason for termination')] - termination_context: Annotated[ - TerminationContext | None, Field(description='Context for the termination') - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description="Opaque correlation data that is echoed unchanged in responses. Used for internal tracking, UI session IDs, trace IDs, and other caller-specific identifiers that don't affect protocol behavior. Context data is never parsed by AdCP agents - it's simply preserved and returned.", - title='Context Object', - ), - ] = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None diff --git a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_response.py b/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_response.py deleted file mode 100644 index 7e80ef4d..00000000 --- a/src/adcp/types/generated_poc/bundled/sponsored_intelligence/si_terminate_session_response.py +++ /dev/null @@ -1,443 +0,0 @@ -# generated by datamodel-codegen: -# filename: bundled/sponsored_intelligence/si_terminate_session_response.json -# timestamp: 2026-06-18T11:28:05+00:00 - -from __future__ import annotations - -from adcp.types._str_enum import StrEnum -from typing import Annotated, Any - -from adcp.types.base import AdCPBaseModel -from pydantic import AnyUrl, AwareDatetime, ConfigDict, Field - - -class Status(StrEnum): - submitted = 'submitted' - working = 'working' - input_required = 'input-required' - completed = 'completed' - canceled = 'canceled' - failed = 'failed' - rejected = 'rejected' - auth_required = 'auth-required' - unknown = 'unknown' - - -class DiscriminatorItem(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - property_name: Annotated[ - str, - Field( - description='Discriminator property name (e.g., `type`, `value_type`). Aligns with OpenAPI 3.x `discriminator.propertyName`.' - ), - ] - value: Annotated[ - str | float | bool | None, - Field( - description="Value the caller sent at `property_name`. Typically a string for const-discriminated unions; numeric/boolean/null permitted. Object and array values are forbidden — const discriminators are scalars, and emitting a structured value would conflate 'caller sent a complex shape' with 'validator inferred from a structural match'." - ), - ] - - -class Issue(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - pointer: Annotated[ - str, - Field( - description="RFC 6901 JSON Pointer to the offending field in the request payload (e.g., '/packages/0/targeting/geo_countries/2'). Format chosen to match Ajv's native validation output (`instancePath`); standardized and unambiguous on keys containing `/` or `~`. NOTE: this differs from the legacy top-level `field` which uses JSONPath-lite (`packages[0].targeting.geo_countries[2]`). When sellers populate `field` from `issues[0].pointer` for backward compatibility (see `field` description), they MUST translate the format — `/packages/0/x` → `packages[0].x`. Future major versions will deprecate `field` in favor of `issues[].pointer`." - ), - ] - message: Annotated[ - str, - Field(description='Human-readable description of why this specific field was rejected.'), - ] - keyword: Annotated[ - str, - Field( - description="Schema keyword that rejected the payload, drawn from the JSON Schema vocabulary (e.g., 'required', 'type', 'format', 'enum', 'pattern', 'minimum', 'maxLength'). Matches the keyword names emitted by JSON Schema validators (Ajv, jsonschema, etc.) so agents can pattern-match on rejection class without parsing message text. Implementers SHOULD use the validator's native keyword name; do not invent custom values here." - ), - ] - schemaPath: Annotated[ - str | None, - Field( - description="Optional. JSON Schema tree path of the rejecting keyword (e.g. '#/properties/packages/items/oneOf/1'). 3.1+ consumers SHOULD prefer `schema_id`; `schemaPath` is retained for 3.0.x compatibility (renamed to `schema_path` in a future major). See error-handling.mdx for the validator-internals production-emit rules." - ), - ] = None - schema_id: Annotated[ - str | None, - Field( - description="Optional. `$id` of the rejecting (sub-)schema (e.g. `/schemas/3.1.0/core/activation-key.json`). MUST resolve to a `$id` published in the spec at the version the seller advertises via `get_adcp_capabilities` — either a deep sub-schema (the typical case) or the response-root `$id` (the bundled-tree fallback for tools served from bundles built before #3868). Sellers MUST NOT emit when the rejection occurred against a private extension, server-only sub-schema, or pre-release element — the public-spec replay rationale only holds when the rejecting element is reachable from the public bundle. Sellers populating `schemaPath` SHOULD also populate `schema_id` when they have it so 3.1+ readers don't get strictly less than 3.0.x readers. See error-handling.mdx for resolution guidance and the bundled-tree caveat." - ), - ] = None - discriminator: Annotated[ - list[DiscriminatorItem] | None, - Field( - description="Optional. Const-discriminator property/value pair(s) identifying the variant the validator selected from values present in the payload. Sellers MUST populate only when (a) the rejecting schema is a const-discriminated `oneOf` / `anyOf` and (b) the discriminator property is present in the payload — emission on partial-match inference would fingerprint the seller's validator implementation. MUST omit when zero variants survive. Compound discriminators (e.g. `(type, value_type)`) produce multiple entries ordered by declaration in the rejecting schema's `properties` block. Same private-extensions / version-skew carve-out as `schema_id`. See error-handling.mdx." - ), - ] = None - - -class Recovery(StrEnum): - transient = 'transient' - correctable = 'correctable' - terminal = 'terminal' - - -class Source(StrEnum): - producer = 'producer' - sdk = 'sdk' - - -class AdcpError(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class Scheme(StrEnum): - Bearer = 'Bearer' - HMAC_SHA256 = 'HMAC-SHA256' - - -class Authentication(AdCPBaseModel): - model_config = ConfigDict( - extra='forbid', - ) - schemes: Annotated[ - list[Scheme], - Field( - description="Array of authentication schemes. Supported: ['Bearer'] for simple token auth, ['HMAC-SHA256'] for legacy shared-secret signing. Both are deprecated; new integrations SHOULD omit `authentication` and use the RFC 9421 webhook profile.", - max_length=1, - min_length=1, - ), - ] - credentials: Annotated[ - str, - Field( - description='Credentials for the legacy scheme. For Bearer: token sent in Authorization header. For HMAC-SHA256: shared secret used to generate signature. Minimum 32 characters. Exchanged out-of-band during onboarding.', - min_length=32, - ), - ] - - -class PushNotificationConfig(AdCPBaseModel): - url: Annotated[ - AnyUrl, - Field( - description='Webhook endpoint URL for task status notifications. The wire contract is unconstrained beyond `format: "uri"` — in particular, publishers SHOULD NOT enforce a destination-port allowlist by default, since buyers legitimately host receivers on non-standard TLS ports (`:9443`, `:4443`, path-routed multi-tenant gateways). The SSRF guard the protocol relies on is the IP-range check + DNS-rebinding-resistant connect pin defined in [Webhook URL validation (SSRF)](/docs/building/by-layer/L1/security#webhook-url-validation-ssrf), not port filtering. Operators who want a hardened destination-port allowlist as defense-in-depth (e.g., locked-down enterprise egress) opt in explicitly — see [Destination port: permissive by default](/docs/building/by-layer/L1/security#destination-port-permissive-by-default).' - ), - ] - operation_id: Annotated[ - str | None, - Field( - description="Buyer-supplied correlation identifier for the operation that will produce webhooks against this registration. The seller MUST echo this value verbatim into every webhook payload's `operation_id` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) and [Webhooks — Operation IDs](/docs/building/by-layer/L3/webhooks#operation-ids-and-url-templates)). Buyers SHOULD generate a unique value per task invocation (UUID recommended). This field is the canonical registration channel for `operation_id`; buyers MAY additionally embed routing values in the URL path or query as an aid for their own HTTP server, but the URL is opaque to the seller and the wire-level source of truth is this field. Sellers MUST NOT parse the URL to recover `operation_id`. Sellers that receive a webhook registration without `operation_id` MAY reject the task with `INVALID_REQUEST`.", - max_length=255, - min_length=1, - pattern='^[A-Za-z0-9_.:-]{1,255}$', - ), - ] = None - token: Annotated[ - str | None, - Field( - description="Optional client-provided token for webhook validation. The seller MUST echo this value verbatim in every webhook payload's `token` field (see [`mcp-webhook-payload.json`](/schemas/core/mcp-webhook-payload.json) for the receiver-side validation obligation). Length bounds give receivers a defensive range check on the echoed value; senders SHOULD generate tokens with at least 128 bits of entropy (≥22 base64url characters). This is a complementary authenticity mechanism that can layer on top of the RFC 9421 webhook signature — unlike the `authentication` block below, it is not on the 4.0 removal track. Receivers that registered both a signing key (RFC 9421) and a `token` MUST NOT treat a valid token echo as authorization to skip signature verification; both checks remain independent obligations.", - max_length=4096, - min_length=16, - ), - ] = None - authentication: Annotated[ - Authentication | None, - Field( - description='Legacy authentication configuration (A2A-compatible). Opts the seller into Bearer or HMAC-SHA256 signing instead of the default RFC 9421 webhook profile. Deprecated; removed in AdCP 4.0. **Precedence is a switch, not a fallback:** presence of this block selects the legacy scheme; absence selects 9421. A seller MUST NOT sign the same webhook both ways, and a buyer MUST NOT attempt \'try 9421 first, fall back to HMAC\' verification — signature mode is determined solely by whether this block was present at registration time. The seller\'s baseline 9421 webhook key is published at its brand.json `agents[]` `jwks_uri` using `adcp_use: "request-signing"` (deprecated `webhook-signing` keys remain accepted during the compatibility window); it does not override this selector and is only used when `authentication` is omitted. See docs/building/by-layer/L1/security.mdx#webhook-callbacks for the full precedence and downgrade-resistance rules (including the `webhook_mode_mismatch` rejection a buyer MUST apply when a received webhook\'s signing mode does not match the registered mode).' - ), - ] = None - - -class SessionStatus(StrEnum): - active = 'active' - pending_handoff = 'pending_handoff' - complete = 'complete' - terminated = 'terminated' - - -class AcpHandoff(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - checkout_url: Annotated[ - AnyUrl | None, - Field( - description="Brand's ACP checkout endpoint. Hosts MUST validate this is HTTPS before opening." - ), - ] = None - checkout_token: Annotated[ - str | None, - Field( - description='Opaque token for the checkout flow. The host passes this to the checkout endpoint to correlate the SI session with the transaction.' - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Rich checkout context to pass to the ACP endpoint (product details, applied offers, pricing). Alternative to checkout_token for integrations that need structured data.' - ), - ] = None - expires_at: Annotated[ - AwareDatetime | None, - Field( - description='When this handoff data expires. Hosts should initiate checkout before this time.' - ), - ] = None - - -class Action(StrEnum): - save_for_later = 'save_for_later' - set_reminder = 'set_reminder' - subscribe_updates = 'subscribe_updates' - none = 'none' - - -class FollowUp(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - action: Action | None = None - data: Annotated[dict[str, Any] | None, Field(description='Data for follow-up action')] = None - - -class Issue86(Issue): - pass - - -class Error(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - code: Annotated[ - str, - Field( - description='Error code for programmatic handling. The error-code vocabulary is open: `error.code` is wire-typed `string` (not a closed enum), the standard codes published in `enums/error-code.json` are documentary, and senders MAY emit codes outside that set (platform-specific codes, or codes introduced in a later AdCP version). Receivers MUST decode unknown codes — treat the response as well-formed, read `error.recovery` for the recovery classification, and fall back to `transient` when `recovery` is absent. See `error-handling.mdx#forward-compatible-decoding-normative` for the full forward-compat contract — this rule is what lets future maintenance lines ship new codes additively.', - max_length=64, - min_length=1, - ), - ] - message: Annotated[str, Field(description='Human-readable error message')] - field: Annotated[ - str | None, - Field( - description="Field path associated with the error in JSONPath-lite format (e.g., 'packages[0].targeting'). When `issues[]` is also present, sellers MUST set this to `issues[0].pointer` translated from RFC 6901 to JSONPath-lite (e.g., '/packages/0/targeting' → 'packages[0].targeting') so pre-3.1 consumers reading `field` only get deterministic behavior. Will be deprecated in a future major version in favor of `issues[].pointer`." - ), - ] = None - suggestion: Annotated[str | None, Field(description='Suggested fix for the error')] = None - retry_after: Annotated[ - float | None, - Field( - description='Seconds to wait before retrying the operation. Sellers MUST return values between 1 and 3600. Clients MUST clamp values outside this range.', - ge=1.0, - le=3600.0, - ), - ] = None - issues: Annotated[ - list[Issue86] | None, - Field( - description='Structured list of validation failures. Primary use is `VALIDATION_ERROR`, where multi-field rejections are common and `field` (singular) cannot carry the full pointer map. MAY appear on other error codes that reject multiple fields at once. When `issues` is present, sellers MUST also populate `field` from `issues[0]` for backward compatibility with pre-3.1 consumers that read `field` only — translating the RFC 6901 `pointer` format to the JSONPath-lite format `field` uses (e.g., `/packages/0/targeting` → `packages[0].targeting`). MUST (not SHOULD) so consumers reading `field` get deterministic behavior across sellers — the cost is one line of dual-write per seller; the cost of SHOULD is a long tail of seller-A-vs-seller-B inconsistency. Future major versions will deprecate `field` in favor of `issues[].pointer`.' - ), - ] = None - details: Annotated[ - dict[str, Any] | None, - Field( - description='Additional task-specific error details. Sellers MAY mirror `issues[]` here as `details.issues` for backward compatibility with pre-3.1 consumers reading from `details`; new consumers SHOULD prefer the top-level `issues` field.\n\n**Canonical rejection-set shape (3.1+).** When the error reports a rejected value against a closed set of accepted values (e.g., enum mismatch, unsupported pricing option, invalid signal id), sellers SHOULD use the canonical key `accepted_values: ` under `details` rather than seller-specific variants observed in the wild (`available`, `allowed`, `accepted_values` at the error root, etc.). The canonical shape:\n\n```json\n{\n "code": "INVALID_PRICING_MODEL",\n "message": "Pricing option not found: po_prism_abandoner_cpm",\n "field": "pricing_option_id",\n "details": {\n "rejected_value": "po_prism_abandoner_cpm",\n "accepted_values": ["po_prism_cart_cpm", "po_prism_view_cpm"]\n }\n}\n```\n\n- `rejected_value` (optional): the offending value the buyer supplied, echoed for buyer-side diagnostic clarity (especially when the offending field is nested or transformed before validation).\n- `accepted_values` (optional): the closed set the seller would have accepted at this field on this call. Sellers MUST NOT enumerate the full ecosystem-wide accepted set if it differs from what\'s accepted for *this caller in this context* (account, brand, scope) — leaking ecosystem-wide accepted sets to a per-caller rejection turns the error into an enumeration oracle.\n\nThis is **SHOULD-level guidance**, not MUST: `details` remains `additionalProperties: true` and pre-3.1 sellers using `available` / `allowed` / `accepted_values` at the error root remain conformant. The canonical shape lets buyer-side diagnostic tooling (SDK runner hints, dashboards, error classifiers) reliably surface the accepted-set without per-seller pattern matching. SDKs SHOULD accept any of the legacy variants and normalize on read; the canonical shape is what new sellers and 3.1+ adopters should emit going forward.' - ), - ] = None - recovery: Annotated[ - Recovery | None, - Field( - description='Agent recovery classification. transient: retry after delay (rate limit, service unavailable, timeout). correctable: fix the request and resend (invalid field, budget too low, creative rejected). terminal: requires human action (account suspended, payment required, account not found). Senders SHOULD populate `recovery` on every error from 3.1 onward — it is the normative carrier of recovery semantics across version skew. A receiver that does not recognize `error.code` (a newer code, or a platform-specific code) MUST still be able to classify the error from `recovery`. The `enumMetadata.recovery` block in `enums/error-code.json` is the documentary mirror for known codes; `error.recovery` on the wire is authoritative.' - ), - ] = None - source: Annotated[ - Source | None, - Field( - description='Who emitted this error entry. `producer` (default when absent): emitted by the response\'s authoring agent (the seller for `get_products`, the creative agent for `build_creative`, etc.). `sdk`: augmented by a consuming SDK that detected a non-fatal advisory condition on consumption (e.g., `FORMAT_PROJECTION_FAILED` when the buyer SDK couldn\'t project a v1 format to a canonical, or `FORMAT_DECLARATION_DIVERGENT` when the SDK detected a producer bug on read). SDK-augmented entries SHOULD also set `sdk_id` so downstream consumers can identify which intermediate processor inserted the entry.\n\n**Multi-hop propagation (normative).** AdCP is a federated agent network — responses commonly traverse multiple SDKs (e.g., sales agent → interchange → DSP → buyer). When an SDK augments `errors[]` with a consumption-detected entry, the augmented response carries the entry forward to subsequent hops. Each hop that detects the same condition independently SHOULD deduplicate by `(code, field)` rather than re-emit; the existing entry\'s `sdk_id` identifies which earlier processor saw it first. Producer entries (those without `source: "sdk"`) are authoritative for what the response\'s authoring agent self-detected; SDK entries are observations made on top.\n\n**Replay/audit safety.** Persisted or replayed responses carry `source` and `sdk_id` so the audit trail can distinguish seller-emitted entries from SDK-augmented ones. Without `source`, a downstream consumer can\'t tell whether a code came from the seller or an intermediate SDK, which corrupts attribution.' - ), - ] = None - sdk_id: Annotated[ - str | None, - Field( - description='Optional identifier for the SDK that augmented this error entry. Format: `@` (e.g., `@adcontextprotocol/adcp@7.3.0`, `adcontextprotocol-adcp-python@1.2.0`). MUST be set when `source: "sdk"`; MUST be absent when `source: "producer"` or absent. Lets downstream consumers identify which intermediate processor inserted the entry, useful for debugging cross-SDK divergence (e.g., one SDK detects a projection failure that another SDK\'s registry version doesn\'t).' - ), - ] = None - - -class SiTerminateSessionResponse(AdCPBaseModel): - model_config = ConfigDict( - extra='allow', - ) - adcp_version: Annotated[ - str | None, - Field( - description='Release-precision AdCP version (VERSION.RELEASE, e.g. "3.0", "3.1", "3.1-beta"). On a request: the buyer\'s release pin — the seller validates against its supported_versions and returns VERSION_UNSUPPORTED on cross-major mismatch, or downshifts to the highest supported release within the same major. On a response: the release the seller actually served — clients SHOULD validate the response against that release\'s schema, not against their pin. Patches are not negotiated; surface them as build_version on capabilities for operational visibility. When omitted, falls back to adcp_major_version (deprecated) or server default. Buyers SHOULD emit both adcp_version and adcp_major_version through 3.x to remain compatible with sellers that only read the legacy field. NORMALIZATION: SDKs that read full-semver values from bundle metadata (e.g. ComplianceIndex.published_version = "3.1.0-beta.1") MUST normalize to release-precision ("3.1-beta.1") before emitting on the wire — meta-field values are NOT valid wire values.', - examples=['3.0', '3.1', '3.1-beta', '3.1-rc.1'], - pattern='^\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?$', - ), - ] = None - adcp_major_version: Annotated[ - int | None, - Field( - description="DEPRECATED in favor of adcp_version (release-precision string). Servers MUST continue to honor this field through 3.x. Removed in 4.0. Original semantics: the AdCP major version the buyer's payloads conform to. Sellers validate against their supported major_versions and return VERSION_UNSUPPORTED if unsupported. When omitted, the seller assumes its highest supported version.", - ge=1, - le=99, - ), - ] = None - context_id: Annotated[ - str | None, - Field( - description='Session/conversation identifier for tracking related operations across multiple task invocations. Managed by the protocol layer to maintain conversational context. Distinct from `context` (per-request opaque echo, see below).' - ), - ] = None - context: Annotated[ - dict[str, Any] | None, - Field( - description='Per-request opaque caller-supplied correlation object echoed unchanged in the response. Used for buyer-side tracking (UI session IDs, trace IDs, custom metadata) that the agent MUST preserve byte-for-byte without parsing. Distinct from `context_id` (server-managed session identifier) — `context` is caller-owned echo, `context_id` is server-owned session scope. Both MAY appear on the same response.\n\n**Relationship to per-task body-level `context` declarations.** Many task request/response schemas (147 as of 3.1) already declare a body-level `context` field that `$ref`s `/schemas/core/context.json` at the body root. Under the flat-on-the-wire MCP serialization (see `notes` below), envelope-level `context` and body-level `context` occupy the same key on the response root — they are NOT separate fields, they MUST share the same value, and they MUST both `$ref` `core/context.json`. The envelope declaration is **authoritative** for the schema definition; per-task body declarations are mirrors retained for tooling reasons (SDK codegen completeness, per-task validation against the response schema in isolation). Future versions MAY drop body-level `context` declarations from per-task schemas; conformance does not require either declaration to be present, only that the wire value `$ref`s `core/context.json`.', - title='Context Object', - ), - ] = None - task_id: Annotated[ - str | None, - Field( - description='Unique identifier for tracking asynchronous operations. Present when a task requires extended processing time. Used to query task status and retrieve results when complete.' - ), - ] = None - status: Annotated[ - Status, - Field( - description='Current task execution state. Indicates whether the task is completed, in progress (working), submitted for async processing, failed, or requires user input. REQUIRED on every task response envelope. Synchronous tasks (including read-only metadata calls like `get_adcp_capabilities`) MUST emit `status: "completed"`; async tasks emit `submitted`, `working`, `input-required`, etc. per their lifecycle. Agents MUST NOT emit the legacy task_status or response_status fields alongside this field — the status field is the single authoritative task state.', - title='Task Status', - ), - ] - message: Annotated[ - str | None, - Field( - description='Human-readable summary of the task result. Provides natural language explanation of what happened, suitable for display to end users or for AI agent comprehension. Generated by the protocol layer based on the task response.' - ), - ] = None - timestamp: Annotated[ - AwareDatetime | None, - Field( - description='ISO 8601 timestamp when the response was generated. Useful for debugging, logging, cache validation, and tracking async operation progress.' - ), - ] = None - replayed: Annotated[ - bool | None, - Field( - description="Set to true when this response was returned from the idempotency cache rather than from a fresh execution. Set to false (or omitted) when the request was executed fresh. Buyers use this to distinguish cached replays from new executions — matters for billing reconciliation, audit logs, state-machine routing (cached state-tracking fields are historical snapshots, not current state — re-read via the resource's read endpoint), and any downstream system that assumes exactly-once event semantics. From 3.1 onward, `replayed` MAY appear on responses to any request that resolved via the idempotency cache, including read tools — universal `idempotency_key` (see security.mdx §Idempotency) means the cache holds read responses too." - ), - ] = False - adcp_error: Annotated[ - AdcpError | None, - Field( - description="Transport-envelope error signal for fatal task failures. Per the two-layer model in `error-handling.mdx#envelope-vs-payload-errors-the-two-layer-model`, a fatal task failure SHOULD populate both this envelope-level field AND the payload's `errors[]` array — the envelope carries a typed, extractable error so MCP/A2A clients can dispatch without re-parsing the payload, while the payload's structured `errors[]` remains the canonical normative shape. Non-fatal warnings populate ONLY `payload.errors[]` with `severity: warning` — the envelope MUST NOT carry `adcp_error` for non-failures.", - title='Error', - ), - ] = None - push_notification_config: Annotated[ - PushNotificationConfig | None, - Field( - description='Push notification configuration for async task updates (A2A and REST protocols). Echoed from the request to confirm webhook settings. Specifies URL, authentication scheme (Bearer or HMAC-SHA256), and credentials. MCP uses progress notifications instead of webhooks.', - title='Push Notification Config', - ), - ] = None - governance_context: Annotated[ - str | None, - Field( - description="Governance context token issued by the account's governance agent during check_governance. Buyers attach it to governed purchase requests (media buys, rights acquisitions, signal activations, creative services); sellers persist it and include it on all subsequent governance calls for that action's lifecycle. An account binds to one governance agent (see sync_governance); governance is phased across `purchase` / `modification` / `delivery`, not partitioned across specialist agents, so the envelope carries a single token for the full lifecycle.\n\nValue format: governance agents MUST emit a compact JWS per the AdCP JWS profile (see Security — Signed Governance Context). Sellers MAY verify; sellers that do not verify MUST persist and forward the token unchanged. In 3.1 all sellers MUST verify. Non-JWS values from pre-3.0 governance agents are deprecated.\n\nThis is the primary correlation key for audit and reporting across the governance lifecycle.", - max_length=4096, - min_length=1, - pattern='^[\\x20-\\x7E]+$', - ), - ] = None - payload: Annotated[ - dict[str, Any] | None, - Field( - description='Conceptual grouping for the task-specific response data defined by individual task response schemas (e.g., get-products-response.json, create-media-buy-response.json). `payload` is a documentary construct — it is NOT a required wire field, and its on-the-wire shape depends on transport (see Transport serialization below). Task response schemas declare body fields without wrapping them in a `payload` object; the wire representation places those body fields per transport convention. On MCP the body fields appear as siblings of envelope fields at the root of the tool response; on A2A they appear inside `task.artifacts[0].parts[].DataPart`; on REST they appear at the root of the JSON body.' - ), - ] = None - session_id: Annotated[str, Field(description='Terminated session identifier')] - terminated: Annotated[bool, Field(description='Whether session was successfully terminated')] - session_status: Annotated[ - SessionStatus | None, - Field( - description="Resulting session state. 'complete' for handoff_transaction/handoff_complete, 'terminated' for user_exit/session_timeout/host_terminated.", - title='SI Session Status', - ), - ] = None - acp_handoff: Annotated[ - AcpHandoff | None, - Field(description='ACP checkout handoff data. Present when reason is handoff_transaction.'), - ] = None - follow_up: Annotated[FollowUp | None, Field(description='Suggested follow-up actions')] = None - errors: list[Error] | None = None - ext: Annotated[ - dict[str, Any] | None, - Field( - description='Extension object for platform-specific, vendor-namespaced parameters. Extensions are always optional and must be namespaced under a vendor/platform key (e.g., ext.gam, ext.roku). Used for custom capabilities, partner-specific configuration, and features being proposed for standardization.', - title='Extension Object', - ), - ] = None